-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
[DataSource] Support section expanding and collapsing #33
Comments
I used |
Simulator.Screen.Recording.-.iPhone.15.-.2024-09-04.at.20.35.33.mp4 |
That video looks great!
Hm... I was imagining this would be implemented at the section level, where we add some new configuration properties to However, I do not have any experience with these APIs. I see that the docs mentions a "parent item" — I'm assuming that's why you're suggesting modifying Ideas:
I'm not sure what's best until I play around with this a bit more. |
From the video, |
Simulator.Screen.Recording.-.iPhone.15.-.2024-09-05.at.10.11.24.mp4 |
|
From my understanding of the APIs, that's correct - sections don't have a concept of expanding/collapsing. To achieve the effect, you must use section snapshots, and you have to append items to other items (or to IMO - and I preface this with the fact that I've only spent less than a day messing with the library lol - it seems like it'd be nice if the view model structure continued to match the end state, i.e.
but I don't know if this makes the most sense. |
@jessesquires I hacked together this branch of what I was roughly thinking https://github.com/bmarkowitz/ReactiveCollectionsKit/tree/feature/expandable-sections Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-10-27.at.14.52.05.mp4 |
It's great, I think there are some improvements that can be made:
|
Good call. That being said... I wonder if |
protocol CellViewModel {
var children: [AnyCellViewModel] { get }
}
struct AnyCellViewModel: CellViewModel {
var children: [AnyCellViewModel] { self._children }
private let _children: [AnyCellViewModel]
} |
Yeah, that definitely makes sense. But, I think that would force a consumer to always have to type erase the children. Whereas when you're providing cells to a SectionViewModel, you only have to type erase when providing mixed types. So, something like this:
|
|
I added some additional rough commits to this branch, continuing to play around with this. Some notes:
Would love feedback here as these are just some rough commits to get things at least working. Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-10-29.at.22.12.47.mp4 |
Very nice. The |
Hey @bmarkowitz and @nuomi1 -- sorry for the delay here and thanks for all of your thought and effort on this! I glanced through the code and I have a new idea. I haven't experimented with this yet, so I'm not sure if it would work. I'm still apprehensive to add
I haven't tested this, but here's a rough draft for the snapshot code (building on what @bmarkowitz has already done): typealias DiffableSnapshot = NSDiffableDataSourceSnapshot<AnyHashable, AnyHashable>
typealias DiffableSectionSnapshot = NSDiffableDataSourceSectionSnapshot<AnyHashable>
extension DiffableSnapshot {
init(viewModel: CollectionViewModel) {
self.init()
let allSectionIdentifiers = viewModel.sections.map(\.id)
self.appendSections(allSectionIdentifiers)
viewModel.sections.forEach {
let allCellIdentifiers = $0.cells.map(\.id)
// NEW: check the property and only add the first item
if $0.isExpandableCollapsible, let first = allCellIdentifiers.first {
self.appendItems([first], toSection: $0.id)
} else {
self.appendItems(allCellIdentifiers, toSection: $0.id)
}
}
}
}
extension DiffableSectionSnapshot {
init(viewModel: SectionViewModel) {
self.init()
let allCellIdentifiers = viewModel.cells.map(\.id)
// NEW: for sections, check the property and build the snapshot accordingly
if viewModel.isExpandableCollapsible,
viewModel.count > 1,
let first = allCellIdentifiers.first {
self.append([first])
let children = Array(allCellIdentifiers.dropFirst())
self.append(children, to: first)
} else {
self.append(allCellIdentifiers)
}
}
}
I think this should work as expected, right? Or am I missing something? |
@jessesquires No worries - thanks for taking a look! I think this would definitely work - I'm just a little unclear on the change in the // NEW: check the property and only add the first item
if $0.isExpandableCollapsible, let first = allSectionIdentifiers.first {
self.appendItems([first], toSection: $0.id)
}
|
Oops. Yes. That was a typo. Fixed.
Ah, that might be true? Again, I didn't test this yet. I made some assumptions: In
Perhaps those assumptions are incorrect? |
Yeah, that's my understanding based on the overview here and the fact that this API only exists on section snapshots. This is where I started leaning towards building on CellViewModel rather than SectionViewModel. The underlying diffable data source API is built on the idea that items are expandable/collapsable, and items are nested within items, rather than sections. So just thinking that if someone comes in knowing how that works, this would be a different way of thinking. I'm also not sure if we want to assume for the consumer that the first item is the one that things will be nested within. Maybe we're fine with those things, but just throwing it out there - I kinda liked the idea that having CellViewModels nested within CellViewModels would more closely match how things would actually look in the UI (thinking of the file directory example from earlier). |
To clarify, you could decide to literally have only one section with 50 items in it, and all 50 of those items could be expandable with varying amounts of children, and so on. |
@bmarkowitz Ohh I just realized — nested items can have nested items. So if we use my "section hack" then we would be limiting users to a single level of nesting. 🤦🏼♂️ Ok, I agree the right approach here is for Let's use @nuomi1's suggestion above: protocol CellViewModel {
var children: [AnyCellViewModel] { get }
}
struct AnyCellViewModel: CellViewModel {
var children: [AnyCellViewModel] { self._children }
private let _children: [AnyCellViewModel]
} Note: we don't think we can use It would also be nice to have: extension CellViewModel {
var isParent: Bool {
self.children.isNotEmpty
}
} |
As for a PR for this: Maybe let's go ahead and open one up? Then we can move our discussion about implementation details there. Also, for the example app for this PR:
|
Sounds good! One last question for you - My only confusion with Do we want to require them to be type erased up front, or should it be handled when the |
@bmarkowitz Great question. Re-posting your snippet above for reference: protocol CellViewModel {
var children: [any CellViewModel] { get }
}
struct AnyCellViewModel: CellViewModel {
var children: [any CellViewModel] { self._children }
private let _children: [AnyCellViewModel]
public init<T: CellViewModel>(_ viewModel: T) {
self._children = children.map { $0.eraseToAnyViewModel() }
}
}
This is correct about optimizing a homogeneous array of children. And I do like this idea. However, the downside here is that if we use In fact, I don't think |
Gotcha, gotcha. Great point, I hadn't thought about that. I have a lot to learn about existentials 😄 |
I'll do my best to get an initial PR up relatively soon, so we can start discussing some more 👍 |
@bmarkowitz No rush! 😊 |
Docs:
Sample code:
The text was updated successfully, but these errors were encountered: