Develop in Swift Data Collections

Guided Project: Habits - 3

GayoonKim 2024. 6. 14. 19:23

Displaying a Collection of Users

User 구조체 정의

struct User {
    let id: String
    let name: String
    let color: Color?
    let bio: String?
}

extension User: Codable {}

extension User: Hashable {
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: User, rhs: User) -> Bool {
        lhs.id == rhs.id
    }
    
}

 

Settings.swift 파일 Setting 열거형에 "followedUserIDs" key 추가 및 followed users IDs를 불러오고 저장하기 위한 프로퍼티 선언

enum Setting {
    static let favoriteHabits = "favoriteHabits"
    static let followedUserIDs = "followedUserIDs"
}

var followedUserIDs: [String] {
        get {
            return unarchiveJSON(key: Setting.followedUserIDs) ?? []
        }
        set {
            archiveJSON(value: newValue, key: Setting.followedUserIDs)
        }
}

 

UserCollectionViewController에 User 관련 기본 정의

    typealias DataSourceType = UICollectionViewDiffableDataSource<ViewModel.Section, ViewModel.Item>
    
    enum ViewModel {
        typealias Section = Int
        
        struct Item: Hashable {
            let user: User
            let isFollowed: Bool
            
            func hash(into hasher: inout Hasher) {
                hasher.combine(user)
            }
            
            static func ==(_ lhs: Item, _ rhs: Item) -> Bool {
                return lhs.user == rhs.user
            }
        }
    }
    
    struct Model {
        var userByID = [String:User]()
        var followedUsers: [User] {
            return Array(userByID.filter{ Settings.shared.followedUserIDs.contains($0.key) }.values)
        }
    }
    
    var dataSource: DataSourceType!
    var model = Model()

Add Networking and Snapshot Code

APIService에 새로운 API Request 타입 정의

struct UserRequest: APIRequest {
    typealias Response = [String: User]
    
    var path: String { "/users" }
}

 

User를 이름에 따라 분류하기 위해 User 구조체 Comparable 프로토콜 채택

extension User: Comparable {
    static func < (lhs: User, rhs: User) -> Bool {
        return lhs.name < rhs.name
    }
}

 

Users 불러오는 메서드 정의

func update() {
        
        usersRequestTask?.cancel()
        usersRequestTask = Task {
            if let users = try? await UserRequest().send() {
                self.model.userByID = users
            } else {
                self.model.userByID = [:]
            }
            self.updateCollectionView()
            
            usersRequestTask = nil
        }
        
}
    
func updateCollectionView() {
        
        let users = model.userByID.values.sorted().reduce(into: [ViewModel.Item]()) { partial, user in
            partial.append(ViewModel.Item(user: user, isFollowed: model.followedUsers.contains(user)))
        }
        
        let itemBySection = [0: users]
        
        dataSource.applySnapshotUsing(sectionIDs: [0], itemsBySection: itemBySection)
        
}

Set Up the Layout and Data Source

HabitCollectionViewController와 비슷하다.

 

createDataSource() 메서드

func createDataSource() -> DataSourceType {
        
        let dataSource = DataSourceType(collectionView: collectionView) { (collectionView, indexPath, item) in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "User", for: indexPath) as! UICollectionViewListCell
            
            var content = cell.defaultContentConfiguration()
            content.text = item.user.name
            content.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 11, leading: 8, bottom: 11, trailing: 8)
            content.textProperties.alignment = .center
            cell.contentConfiguration = content
            
            return cell
        }
        
        return dataSource
        
}

 

createLayout() 메서드

 func createLayout() -> UICollectionViewCompositionalLayout {
        
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.45))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 2)
        group.interItemSpacing = .fixed(20)
        
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 20
        section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
        
        return UICollectionViewCompositionalLayout(section: section)
        
}

 

viewDidLoad()

override func viewDidLoad() {
        
        super.viewDidLoad()
        
        dataSource = createDataSource()
        collectionView.dataSource = dataSource
        collectionView.collectionViewLayout = createLayout()
        
        update()
        
}

Implement the User Following Context Menu

Settings

mutating func toggleFollowed(user: User) {
        
        var updated = followedUserIDs
        
        if updated.contains(user.id) {
            updated = updated.filter { $0 != user.id }
        } else {
            updated.append(user.id)
        }
        
        followedUserIDs = updated
        
}

 

UserCollectionViewController

override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
        
        let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (elements) -> UIMenu? in
            guard let item = self.dataSource.itemIdentifier(for: indexPaths.first!) else  { return nil }
            
            let favoriteToggle = UIAction(title: item.isFollowed ? "Unfollowed" : "Follow") { (action) in
                Settings.shared.toggleFollowed(user: item.user)
                self.updateCollectionView()
            }
            
            return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [favoriteToggle])
        }
        
        return config
        
}

 

 

 

'Develop in Swift Data Collections' 카테고리의 다른 글

Guided Project: Habits - 5  (0) 2024.06.18
Guided Project: Habits - 4  (0) 2024.06.17
Guided Project: Habits - 2  (0) 2024.06.13
Guided Project: Habits - 1  (0) 2024.06.13
Lesson 3.5 Local Notifications - Practice(Alarm)  (0) 2024.06.06