Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performing Nested Adapter Updates on Section Controllers/Binding Section Controllers #1264

Closed
2 tasks done
jboo1212 opened this issue Oct 18, 2018 · 3 comments
Closed
2 tasks done
Labels

Comments

@jboo1212
Copy link

jboo1212 commented Oct 18, 2018

New issue checklist

General information

  • IGListKit version: 3.0
  • iOS version(s): 11.4
  • CocoaPods/Carthage version: 1.5.2
  • Xcode version: 10.0
  • Devices/Simulators affected: iPhone 8
  • Reproducible in the demo project? (Yes/No): Yes
  • Related issues: Nested adapter updates

In https://github.com/jboo1212/IGStoriesClone I'm making a stories clone but in my finals steps of completing the clone, I'm having difficulties wrapping my brain around nested adapter updates. Whenever the collection view is dragged by its refresh control, it should trigger an adapter update from Story Model to the nested adapter data source containing the stories.

I.e. when a story is "read" by the user and the collection view is dragged, the system will move read stories towards the end of the collection and not read stories towards the front.

Am I on the right track? Should I make Story Model have a better isEqual(toDiffableObject object: ListDiffable?) -> Bool ?

Below are the models for IGStories.

final class StoryModel: ListDiffable {
    
    let stories: [Story]
    
    init(stories: [Story]) {
        self.stories = stories
    }
    
    func diffIdentifier() -> NSObjectProtocol {
        return "StoryModel" as NSObjectProtocol
    }
    
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard self !== object else { return true }
        guard let object = object as? StoryModel else { return false }
        return stories == object.stories
    }
}
/*
 Each story consists of a User, whether or not we've read the current Story, and a list of story items which
 represent the AVAssets or AVPlayerItems in the current Story we're watching.
 */
final class Story: ListDiffable {
    
    let user: User
    let isRead: Bool
    let storyItems: [StoryItem]
    
    init(user: User, isRead: Bool, storyItems: [StoryItem]) {
        self.user = user
        self.isRead = isRead
        self.storyItems = storyItems
    }
    
    // A story is unique from another story based on the identity of the current User.
    func diffIdentifier() -> NSObjectProtocol {
        return user.diffIdentifier()
    }
    
    // The cause of the Story Section Controller to update will be the addition of new Story Items.
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard self !== object else { return true }
        guard let object = object as? Story else { return false }
        return isRead == object.isRead && storyItems == object.storyItems
    }
    
    func toInteger(fromBool bool: Bool) -> Int {
        let value: Int
        value = bool == false ? 0: 1
        return value
    }
}

extension Story: Equatable {
    static func == (lhs: Story, rhs: Story) -> Bool {
        return lhs.isRead == rhs.isRead && lhs.storyItems == rhs.storyItems && lhs.user == rhs.user
    }
}

/*
 A story item can either be: a still image or a video with some length.
 Both are URL's.
 */
final class StoryItem: ListDiffable {
    
    let id: String
    let url: URL
    
    init(id: String, url: URL) {
        self.id = id
        self.url = url
    }
    
    func diffIdentifier() -> NSObjectProtocol {
        return id as NSObjectProtocol
    }
    
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        return true
    }
}

// MARK: Equatable

extension StoryItem: Equatable {
    // Allow for comparison of story items in an array of story items.
    static func == (lhs: StoryItem, rhs: StoryItem) -> Bool {
        return lhs.id == rhs.id && lhs.url == rhs.url
    }
}

final class User: ListDiffable {
    
    let id: String
    let profilePic: URL
    let handle: String
    
    init(id: String, profilePic: URL, handle: String) {
        self.id = id
        self.profilePic = profilePic
        self.handle = handle
    }
    
    func diffIdentifier() -> NSObjectProtocol {
        return id as NSObjectProtocol
    }
    
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard self !== object else { return true }
        guard let object = object as? User else { return false }
        return profilePic == object.profilePic && handle == object.handle
    }
}

extension User: Equatable {
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id && lhs.profilePic == rhs.profilePic && lhs.handle == rhs.handle
    }
}
@rnystrom
Copy link
Contributor

@jboo1212 yup! That looks like you're on the right track. We do almost the same thing in Instagram.

@jboo1212
Copy link
Author

Hey @rnystrom, thanks for the confirmation. I just had one issue though. I receive the below call stack error...

2018-11-11 12:50:25.406982-0800 InstagramStoriesClone[38989:832689] *** Assertion failure in -[IGListBindingSectionController didUpdateToObject:](), /Users/jeromeisaacs/Documents/Applications/InstagramStoriesClone/Pods/IGListKit/Source/IGListBindingSectionController.m:119
2018-11-11 12:50:25.421124-0800 InstagramStoriesClone[38989:832689] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unequal objects InstagramStoriesClone.Story and InstagramStoriesClone.Story will cause IGListBindingSectionController to reload the entire section'

I trigger an update of the data source to diff the Story Model > Stories that have been read but not sure why it triggers that error.

Below is the code...

    private func modelGenerators() -> [Story] {
        // Users
        let user1 = User(id: UUID().uuidString, profilePic: Bundle.main.url(forResource: "jeromeythehomie", withExtension: "jpg")!, handle: "jeromeythehomie")
        let user2 = User(id: UUID().uuidString, profilePic: Bundle.main.url(forResource: "mattlee077", withExtension: "jpg")!, handle: "mattlee077")
        let user3 = User(id: UUID().uuidString, profilePic: Bundle.main.url(forResource: "asethics", withExtension: "jpg")!, handle: "asethics")
        let user4 = User(id: UUID().uuidString, profilePic: Bundle.main.url(forResource: "nat.pat33", withExtension: "jpg")!, handle: "nat.pat33")
        
        // Story Items
        let storyItem1 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_0021", withExtension: "mov")!)
        let storyItem2 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_0460", withExtension: "mov")!)
        let storyItem3 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1539", withExtension: "mov")!)
        let storyItem4 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1636", withExtension: "mov")!)
        let storyItem5 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1691", withExtension: "mov")!)
        let storyItem6 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1704", withExtension: "mov")!)
        let storyItem7 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1705", withExtension: "mov")!)
        let storyItem8 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1706", withExtension: "mov")!)
        let storyItem9 = StoryItem(id: UUID().uuidString, url: Bundle.main.url(forResource: "IMG_1707", withExtension: "mov")!)

        // Stories
        let story1 = Story(user: user1, isRead: false, storyItems: [storyItem1, storyItem2, storyItem3])
        let story2 = Story(user: user2, isRead: true, storyItems: [storyItem4, storyItem5, storyItem6, storyItem7])
        let story3 = Story(user: user3, isRead: false, storyItems: [storyItem8])
        let story4 = Story(user: user4, isRead: false, storyItems: [storyItem9])
        let stories = [story1, story2, story3, story4]
        
        let story5 = Story(user: user1, isRead: false, storyItems: [storyItem1, storyItem2, storyItem3])
        let story6 = Story(user: user2, isRead: true, storyItems: [storyItem4, storyItem5, storyItem6, storyItem7])
        let story7 = Story(user: user3, isRead: true, storyItems: [storyItem8])
        let story8 = Story(user: user4, isRead: true, storyItems: [storyItem9])
        
        otherStories = [story5, story6, story7, story8]
        
        return stories
    }
    
    @objc private func onRefresh() {
        refreshControl.beginRefreshing()
        let model = StoryModel(stories: otherStories)
        storyModel = [model]
        adapter.performUpdates(animated: true, completion: nil)
        refreshControl.endRefreshing()
    }

My understanding is that the Story Model contains Stories that have changed which will then tell the Stories data source (IGListBindingSectionController) to update. I'll edit my previous post to show you how the models look now.

@rnystrom
Copy link
Contributor

@jboo1212 if you update to use the master branch of IGListKit, this assertion goes away. See PR #1213

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants