README and documentationIGListKit version: LatestI'm still quite nooby with IGListKit, I've been struggling 2-3 days on this issue and I've been reading all the related issues, so really sorry if it's a duplicate. I really need some help, or at least an hint if what I'm doing wrong.
I'm trying to display three sections that can have an unlimited number of rows. Each section would have a title and use the same type of cell. To represent the sections, I've create three datasource that hold an array of items to display and the section title as such:
final class DataSource: NSObject {
var title: String?
var items: [SJResult] = []
}
extension DataSource: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
if object === self {
return true
}
guard let obj = object as? DataSource else {
return false
}
return obj.items.count == items.count
}
}
So in the section controller, I would use items.count to define the number of items in func numberOfItems() -> Int.
The items property is populated in realtime, so each time I receive new item from my API I need to refresh the collection view (no pagination at the moment), so to refresh I've done adapter.performUpdates(animated: true), which result in nothing. If I understood well, this make sense because my Datasource object hasn't changed except that the items property has been updated. The problem is that my items are never displayed and func didUpdate(to object: Any) is never called (except at the first initialisation), the only way I can display my items is by doing adapter.reloadData(), which result in reloading all the collection view and at this point it's impossible to animate the insertion of new items.
I'm a bit stuck of what implementation should I follow to resolve my problem. I've tried to use reloadObjects() as well without any luck and I've tried to embed two section controller (like in the example), the first section having one UICollectionViewCell that embed a IGListCollectionView. I'm now thinking of adding three IGListCollectionView to the parent view and use them separately. I don't really like this option, if you have a better one or can explain me what I'm doing wrong, I'd really appreciate.
By the way, I've been using IGListKIt in much simple layout and it's absolutely awesome, made me earn a lot of time and works like a charm. Thanks!
@Nonouf thanks for this report! If I'm understanding things correctly, you're calling adapter.performUpdates(...) after some API request finishes, and nothing shows up on the screen. But if you call adapter.reloadData() it works. Is that right?
It's a little unclear w/ just the model setup what could be going wrong. Your models are mutable (I see the var) but are you recreating them when items changes or just appending? I'd strongly recommend using immutable models (let) and recreating them when your data changes.
Another observation, you're using self as the diffIdentifier, so even though you're comparing the items.count, that will never execute since your models are uniquely identified based on the instance. (if you're curious, check the source where we only call isEqualToDiffableObject: if different instances).
Hopefully that helps! If not, a sample project would be really useful.
@rnystrom Thank you for your answer!
If I'm understanding things correctly, you're calling adapter.performUpdates(...) after some API request finishes, and nothing shows up on the screen. But if you call adapter.reloadData() it works. Is that right?
Yes that's right. I'm basically polling data from an API every x seconds, so each time I receive new item I need to refresh the list.
Your models are mutable (I see the var) but are you recreating them when items changes or just appending?
No I'm not, all I'm doing is updating the items. I should append new item, but at the moment as a test I've just been updated the property. I'm going to look at recreating a new model a each changes. The thing is I'd like to have each section staying "static" and just update the item inside each section, that's why items is mutable.
Another observation, you're using self as the diffIdentifier, so even though you're comparing the items.count, that will never execute since your models are uniquely identified based on the instance.
Yes I think I didn't totally understand how the primary key works at first and used self. I've been also trying to generate a a new primary key each time I wanted to refresh my sections (based on a UUID and the date), but it didn't work. I think that's when I started to be a bit confused on the role of the primary key, as generating a new one then doing a adapter.performUpdates seemed to do nothing.
I'm going to prepare a test project today and replicate the issue I have.
@Nonouf great! One thing I'll say is that mutating (appending) to items _can work_ (in fact we have places we do this in Instagram!) it's just a little tricky since you need other mechanisms to notify your section controller that things have changed:
itemsNSNotificationCenterAgain, this can be tricky. I think the immutable model, "pass down" approach is the better way to go here. But we will definitely get this working for you!
👍 on a test project, should be able to fix things up lightning fast with that.
@rnystrom Sorry for having not get back to you yet. After discussion with my team, we have decided to leave IGListKit for now :( I'm a bit gutted to be honest, because I really like your project. I think there is a few things we misunderstood with IGListKit, which lead us to mis-architecturing our list. Tell me if I'm wrong, but I really have the feeling that IGListKit is awesome when you want to display a simple list that can have different type of datasource object, but when it comes to display sections with some content using the same type of object (like you could just do with a table view), it starts to become a bit complicated.
Anyway, this issue is stuck in my mind now, so I will definetely try to fix it at some point and I'll get back to you when I can. Thank you very much for your help and keep the great work! :)
@Nonouf IGListKit is plenty powerful enough to support this -- out of the box you are correct though that the default diff/load algorithm takes a flat array and doesn't traverse it any further.
In saying that you can implement your own Adapter Updater and do the diffing however you like but I agree IGListKit loses a lot of it's appeal as soon as you start having to do that manually.
@Nonouf @robertjpayne definitely check out IGListBindingSectionController for 2d diffing and using arrays of view models, might help a lot!
IGListKit is awesome when you want to display a simple list that can have different type of datasource object, but when it comes to display sections with some content using the same type of object (like you could just do with a table view), it starts to become a bit complicated
Hm, not totally sure I follow. We use hundreds/thousands of the same type of models in Instagram, arbitrarily intertwined with other models, and it makes our architecture super clean vs filling view controllers (or even data source objects) with arrays/maps/state.
If all you want is some table with labels and stuff, there's no harm just using UITableView 😄 but soon as you scale past simple lists, that's where IGListKit shines (tho we see huge wins even on simple lists).
On Instagram we have dozens of IGListKit-built products used by hundreds of millions of people with a team of 50+ iOS engineers. That scale absolutely influences our priorities and design! If you have suggestions on how we can improve the approachability of IGListKit, I would love to hear them! 🙌
@rnystrom the more I explored the more powerful IGListKit gets, I actually saw in the readme you recommend 1:1 section to row to help get around these sort of nested reloading issues.
@rnystrom the only thing that becomes tricky is sticky headers, is there any example code based on IGListKit that modifies the collection view layout to do sticky headers? Even with iOS 9.0's new API it's not ideal with IGListKit because of the common 1 row per 1 section mapping and flat list.
@Nonouf I'd empower you to give IGListKit another go if you still have a chance -- the key I found is to use a flat array and use section controllers like view models with a 1:1 mapping rather than trying to do 1:many.
Or check out master branch with the bindable controller which allows the sort of thing this issue notes!
@robertjpayne oh hmm can you point to that? We should definitely update it. Strongly recommend organizing rows under a relative section architecture. e.g. on Instagram we have headers, media, actions, and comments all under a FeedItem controller.
the only thing that becomes tricky is sticky headers, is there any example code based on IGListKit that modifies the collection view layout to do sticky headers?
Definitely check out our new layout that has sticky header support built in. We also use this 😉
It requires having a header-per-section though, so breaking items out into multiple sections wont work. For that I'd go w/ the binding section controller + layout. Should work beautifully!
@rnystrom https://github.com/Instagram/IGListKit/blob/master/Guides/Best%20Practices%20and%20FAQ.md -- last bullet under Best Practices.
Isn't that still sorta the case? Don't mean to hijack this issue but the primary concern of the author was that the root object doesn't change while some part of it's nested contents is dynamic and does change it doesn't get refreshed.
This changes a bit with the bindable controller in master as an example though...
Seems to be some confusion over the term "single-item", we can change that. When we say "item" we mean _object_ or _model_. Note the next sentence
That is, each section controller manages a single model (which may have one or multiple cells).
Definitely not trying to advocate one-cell-per-section design when the same model would drive different cells (like I mentioned above in our FeedItem design).
We can probably do a pass at our examples too to make sure they have a wide-enough range of uses!
@rnystrom ah right, well I do think with the 3.0 bindable section controller it makes it much more clear about how to create a section that has a dynamic reload…
Just came here and wanted to say that I also misunderstood the mapping between section controllers and models. I'm having trouble with diff'ing and cells not updating which landed me here.
In addition to the last bullet under Best Practices, there is also this line in the FAQ that I think contributed to my misunderstanding:
Should I reuse my section controllers between models?
No! IGListKit is designed to have a 1:1 instance mapping between objects and section controllers. IGListKit does not reuse section controllers, and if you do unintended behaviors will occur.
@chadpav can you let me know what about that bullet is confusing, and how you would change it to be more clear?
@rnystrom first, thanks for contributing this back to the community. I'm really loving how IGListKit enables me to build more complex/flexible lists with a maintainable architecture.
I keep rereading the docs and I think the misleading part for me is the "1:1 instance mapping between objects and section controllers". When I read that I hear "create 1 instance of a section controller for each instance of the object you want to display".
This is what I did, but now my items don't refresh when their properties change unless I do something more brute force like 'adapter.reloadData(completion: nil)':
HeaderSectionController ==> "A Section Header"
Instance of ItemSectionController ==> "An instance of Item"
Instance of ItemSectionController ==> "Another instance of Item"
but now I'm reading this thread and I think I should have done something more like:
ListBindingSectionController "ItemViewModel" ==> "Item Header"
==> "Instance of Item"
==> "Another Instance of Item"
Is what I did wrong? Is it just a matter of not understanding IGListDiffable? In the end, my solution is actually working pretty well with the brute force reloadData. But I think it's only because I have < 12 items in the list.