nicklockwood / layout

A declarative UI framework for iOS
MIT License
2.23k stars 97 forks source link

Feature discussion: template-y features #24

Open mhuusko5 opened 7 years ago

mhuusko5 commented 7 years ago

This is likely outside of the current scope of the XML layouts, but I'm curious if you've thought at all about, for lack of a better description, more dynamic template-y features?

For example, an iterating/pasting construct like https://github.com/kylef/Stencil#example could be applied nicely to scenarios like https://github.com/schibsted/layout/blob/master/SampleApp/Examples.xml.

Basically any way to achieve dynamic structure/composition of UI based on state, vs. just configuration/content, without bouncing back up to Swift, could be very interesting.

eimantas commented 7 years ago

I think this might be approached by using layout-specific tags and attributes. e.g.

<LayoutLoop repeats="5">
    <!-- do stuff -->
</LayoutLoop>
nicklockwood commented 7 years ago

This is definitely doable, but I'm not sure how useful it would be. I think there are generally two distinct categories of use-case for this feature:

  1. A small number of repeated template nodes, e.g. for a settings page, or some other form
  2. A large number of repeated template nodes, e.g. for a list of contacts or a timeline

For case 1, the best solution is to create a list of all possible nodes and then show/hide them using the isHidden property. The reason for this is that churning the view hierarchy is expensive in UIKit compared with showing and hiding views, and this is even more true when using Layout because mutating the node hierarchy throws away all the cached constants, expressions, etc.

This isn't currently possible using pure XML, but you can load an array of LayoutNodes from an XML template programmatically and then inject them into another template using an outlet. The syntax suggested above might be a nice way to skip some boilerplate code, but using a non-constant value for repeats would perform poorly, and making that easy to do might encourage people to misuse it.

Another option would be to try to replicate the static UITableView layouts provided by Storyboards. The underlying mechanism for this is private, but in theory I can replicate it fairly easily.

For case 2, you're absolutely better off using a UITableView or UICollectionView due to the recycling mechanism that minimises memory usage and view churn. I could try to replicate that recycling mechanism in Layout, but since it already has good support for UITableView and UICollectionView, I'm not sure that would be worth doing.

What might be worth exploring is a way extend UITableView/UICollectionView with extra attributes that allow the dataSource to be defined using state instead of having to implement the protocol in code. That's definitely something I'm considering. E.g.

<UITableView rows="{arrayOfRowObjects}">
    <!-- row state would automatically be set for each cell from the rows array -->
    <UITableViewCell reuseIdentifier="template" textLabel.text="{row.title}"/>
</UITableView>
mhuusko5 commented 7 years ago

@nicklockwood fair points. You have confirmed my fears about where this could go (abuse/churn) wise if fully dynamic on state. And agreed, straight lists are best kept to table/collection views, and bringing data source/config into the XML could make this smoother.

That said, I still think there could be a valid case here for template expressions to be applied once, based on initial constants or first state, so that layout of certain elements could be conditional (e.g. in the TabBarController example, isHidden wouldn't really cut it) and boilerplate or Swift interaction could be avoided for small sets of repeated/similar elements. But it's likely not as high priority as I first imagined (complex conditional layout is going to need to be done in code anyway, so it's a narrow margin of application).

benkraus commented 6 years ago

Just thought I'd share the situation I'm in: I'm in an interesting place currently, where I have a view that needs to have multiple lists of things, very low counts of items (3-4 each). In this case, a layout loop I think would be preferred, but a table view with a predefined datasource would also work.

At his point I'll need to have 2 table views inside a parent scroll view, disable scrolling for each, and inside the datasource distinguish between them based on the outlet for which to return the data for. Definitely not ideal.