composed-swift / composedui Goto Github PK
View Code? Open in Web Editor NEWA Swift framework for building User Interfaces with the Composed framework.
License: Other
A Swift framework for building User Interfaces with the Composed framework.
License: Other
FunctionBuilder
supportI'd like to add support for the new function builder APIs.
The current implementation requires the creation of a class
with the inclusion of at least 1 method
. In addition, it is required to create at least 1 cell class (and associated XIB
?).
This doesn't sound like a lot but in practice it can still quickly become cumbersome when building reusable code.
Most often its necessary to define your own generic types on top of Composed's options:
final class PeopleSection: ArraySection<Person>, CollectionSectionProvider {
func section(with traitCollection: UITraitCollection) -> CollectionSection {
let cell = CollectionCellElement(section: self, dequeueMethod: .nib(PersonCell.self)) { cell, index, section in
let element = section.element(at: index)
cell.prepare(with: element)
}
return CollectionSection(section: self, cell: cell)
}
}
Even in this rudimentary example, that's still a fair amount of code. What's important to capture here is:
Array
UICollectionView
PersonCell
Add a FunctionBuilder
style DSL to simplify usage for an API consumer as well reduce the code required to achieve the same result.
The following represents an 'idea' rather than a concrete example. There are still a few areas that need to be worked out before this is possible, as such the following code is for demonstration purposes only and is subject to change.
Section(people) { index in
Cell(.fromNib(PersonCell.self) { cell in
cell.prepare(people[index])
}
}
There are a few added benefits to this approach:
Array
)Presentation
One thing to note, Cell
needs to be inferred so the above wouldn't compile until it's inserted into a view. A nice solution to this might look like the following:
struct Dashboard: CollectionView {
var content: Content {
Section(friends) { ... }
Section(family) { ... }
}
}
Using this approach we can see:
struct
removes reference semantics and the potential for retain cyclesUICollectionView
is intended for the presentationUICollectionViewCell
is what we're expecting for our Cell
configurationHeaders & Footers
However another added benefit to using the view to infer our Section
type is that we can also now determine what 'features' our Section
might have.
For example, we know that our UICollectionView
supports header/footer elements:
struct Dashboard: CollectionView {
var header: Header {
Header(.fromNib(PeopleHeader.self)) { header in
header.titleLabel.text = "People"
}
}
// and to add the header to the section...
var content: Content {
Section(header: header) { ... }
}
}
We could even go further, providing a convenience header type:
Section(header: Text("People"))
Static Views
One final advantage over this API is that it makes it trivial to build both dynamic as well as static Section
's.
Section {
Cell(.fromNib(PersonNameCell.self) { cell in
cell.prepare(person.name)
}
Cell(.fromNib(PersonAgeCell.self) { cell in
cell.prepare(person.age)
}
}
It may even be possible to implement
ForEach
such that mixing static and dynamic content would become possible.
In the current implementation we have a few Section
types that define the backing data that will be used for that section.
SingleElementSection
ArraySection
ManagedSection
As well, we have two SectionProvider
types that help us compose sections:
ComposedSectionProvider
SegmentedSectionProvider
Using the proposed implementation, we can see all of these become completely unnecessary.
SingleElementSection
A SingleElementSection
can be replaced by simply defining a static section:
Section {
Cell(.fromNib(PersonNameCell.self) { cell in
cell.prepare(person.name)
}
}
ArraySection & ManagedSection
Both can be defined by simply providing the relevant data to the Section
ComposedSectionProvider
Simply including multiple sections removes the need for a specific type as well.
SegmentedSectionProvider
That leaves us with just one final type. The purpose of this type is to hold onto 1 or more child Section
's while keeping only 1 active at a time.
Well, this is easily achieved with the above implementation simply with the use of conditional statements.
Section(...)
if isVisible {
Section(...)
}
Section(...)
Conclusion
It's now apparent that this implementation should remove the need for an API consumer (at least) to ever have any knowledge of multiple Section
types. Instead focus on providing the data (or not) for a Section
and specifying which Cell
they want to be used for representing the element at a specified index.
As for composition, using Swift knowledge you already have, you should easily be able to build composable Section
's using nothing more than conditional statements.
The use of FunctionBuilder
APIs also removes the additional syntax of brackets and comma's that would otherwise (and currently) litter your code.
ComposedUI
also extends your Section
through the use of protocol conformance to provide behaviours like editing, selection, drag and drop and more.
One potential solution here would be the use of a Modifier
-style API as seen in SwiftUI
:
struct PeopleSection: CollectionSection {
var header: Header {
Text("People")
.attributes(...)
.padding(.horizontal, 20)
}
var content: Content {
Section(header: header, people) { index in
Cell(.fromNib(PersonCell.self)) { ... }
.onSelection { ... }
.onDrag { ... }
.onAppear { ... }
.contextMenu { ... }
}
}
}
There are a few obvious benefits to this approach:
Composed
will likely support SwiftUI and this API will 'feel' a lot more familiar and consistent.This approach is not only simpler and cleaner, its also much more user friendly and discoverable. It removes a few key pain points for users as well:
One last thing...
There are 2 other areas that still need to be thought through.
Layout, can likely be solved by initialising the view with a layout (if required). The bigger question is how to tie individual section layouts to the outer view layout. However we've solved it already, so I'm sure this can be worked through.
Environment variables are passed to functions or closures currently. These could replaced by something more like @Environment
available in SwiftUI where Composed ensures the variables are set automatically for you at the right moment. I'm not sure atm if this is possible or something the compiler does for free in SwiftUI alone. I will need to investigate this further. Alternatively, the functions/closures could continue to provide these values as required.
The current approach to handle section updates and ensure they get propagated up to the coordinator is to use the delegate pattern. In order to support this 1 of 2 things should be considered.
My current instincts are to consider the usage of Combine
and the new Swift diffing APIs, however this would mean dropping support for iOS <13. I'm not adverse to this, but it should be considered carefully.
This is likely to cause breaking API changes and require significant renaming in both ComposedUI and Composed. As such its planned for 2.0.
N/A
It will be great to have cell reordering functionality implemented.
Thanks
When a section is deleted the didDisappear
call on CollectionElementsProvider
is either not made or is made on the wrong CollectionElementsProvider
.
Delete a section.
Correct calls to didDisappear
should be made/should not crash.
CollectionCoordinator.collectionView(_:didEndDisplaying:forItemAt:)
uses the state of the hierarchy after the more recent updates have been applied, meaning the removed cell/section is no longer in the SectionProviderMapping
or the local cachedProviders
.
For example deleting section 1 with a hierarchy of:
Then remove section 1:
CollectionElementsProvider
didDisappear
on section 2 (even though no cells were removed and it's already empty)Or when section 2 is removed:
guard indexPath.section < sectionProvider.numberOfSections else { return }
I don't have time to look at this right now and I'm not as familiar with the SectionProviderMapping
as other parts, can you try and fix this @shaps80?
A suggested solution is to cache the cells and their sections (e.g. cellSectionMap: [UICollectionViewCell: Section]
) but I'd like to know if there's a alternative approach since that might lead to more issues down the line?
A crash can occur when using invalidateAll
followed by an update (I've only triggered this with an insert but others could work, I suspect delete too).
Call invalidateAll
, followed by an insert/delete. I think the invalidateAll
call needs to trigger a layout.
It should not crash.
master
(8ee5b11, 1.0.3 + some fixes)I think the issue comes down to how the API for the delegate and UICollectionView itself differ. My understanding is that calling reloadData
(which is done on an invalidate) does not guarantee that the changes have been fully applied after reloadData
occurs, e.g. a layout pass might be required.
When looking at the docs for performBatchUpdates
it states:
If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call
performBatchUpdates(_:completion:)
.
This would be ok if we were updating the data model inside the updates block, but updates are surrounded by willBeginUpdating
and didEndUpdating
. didEndUpdating
triggers the call to performBatchUpdates
but by this point the models aren't in-sync with what the collection view thinks is true.
So I think when we call reloadData
and then performBatchUpdates
before the layout has occurred the collection view will try to update straight away. This starts with numberOfSections
which will return the new value, but when the collection view then requires for a cell it will eventually hit elementsProvider(for:)
which will crash because the cachedProviders
has not been updated yet because the prepareSections
call occurs inside the performBatchUpdates
.
I tried calling layoutIfNeeded
before performBatchUpdates
. This essentially triggers the same crash since I think that's what performBatchUpdates
is doing internally
I also tried calling collectionView.layoutIfNeeded()
straight after collectionView.reloadData()
which fixes the crash but seems to cause a visual bug so I'm not sure if this can be classed as a fix or not.
Ultimately the fix for me was to remove invalidateAll
and do diffing, but since most of the built-in types use invalidateAll
this isn't really a fix either.
Ultimately I think the fix is to update the delegate in such a way that the model updates can occur inside the performBatchUpdates
call, but this would need some more thought an a major version bump.
Hi,
I have cloned the 1.0.1
release.
But I can't build ComposeUI
because I can't find the type of MoveHandler
.
Did I miss anything?
When adding a section to a ComposedSectionProvider
in a viewDidLoad
after the CollectionCoordinator
has been set as the delegate but before the view has appeared the coordinator will fatalError
in elementsProvider(for:)
with error Fatal error: No UI configuration available for section 2
.
Should not crash
The crash is triggered by collectionView.performBatchUpdates
, but the closure is never executed so prepareSections
never gets a chance to be called.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.