stevestreza / Relayout
- суббота, 20 августа 2016 г. в 03:14:02
Swift
Swift microframework for declaring Auto Layout constraints functionally
Relayout is a Swift microframework to make using Auto Layout easier with static and dynamic layouts.
If you want to build a UI using Apple's UI frameworks today, you have three good options. You can use Auto Layout in Interface Builder, you can use Auto Layout in code and maintain references to those constraints, or you can implement a layout function with layoutSubviews
. Each of these approaches has pros and cons.
layoutSubviews
, you're usually doing the job of Auto Layout - describing the layout of UI elements. But instead of doing it with objects to describe those relationships, you end up describing it with mathematical equations. This can be the most explicit and least error prone, but can be difficult to read and understand later. You also don't pick up features like right-to-left language support. But you get the benefit of having a single method that handles all your layout, regardless of why a layout changed.The fundamental problem behind making dynamic layouts is state transformations. Going from 1 state to 2 is easy (add 2 transformations, A -> B and B -> A). Going from 2 to 3 is still fairly easy (add 4 transformations, B -> C, A -> C, C -> B, and C -> A). But as you add more and more states, you end up doubling the number of transformations you need to account for, all while fully satisfying the constraint system. This exponential growth is unsustainable, especially when you don't really care about the transformations as much as the states themselves.
Wouldn't it be great if we could cherry pick some of the most useful properties of the approaches? Auto Layout is a very good tool because of how descriptive it is, and we should use it, but not in its current form. Let's take a page from React.js and define a function that returns a pile of constraints that we want for a given UI state.
You could define a single object that generates a pile of constraints for your view. Any time anything happens that could possibly change those constraints, throw the old ones out and generate a new set of constraints. That's what Relayout is.
Since the goal of Relayout is to make it easier to use Auto Layout, it tries to have a minimal impact on your app. You can use it in a view controller or within individual views. You can use it for parts of your app, and not use it elsewhere. You can compose layouts together and control them conditionally based on behaviors like UITraitCollection
state. Whatever you want to do.
Relayout is not a tool for creating constraints. That's up to you. Want to use the NSLayoutConstraint
visual formatting language? The iOS 9+ anchor APIs? A third-party library like PureLayout? As long as it returns NSLayoutConstraint
objects, Relayout can use them. But we're not going to be opinionated about how you create them, or whether they come pre-activated or not.
Relayout is also not an alternative or replacement to Auto Layout, but rather an augmentation. It requires the use of Auto Layout, and that means the rules that come along with it. So if you hate Auto Layout and don't want to use it, then Relayout may not be for you (though you may find that the reasons you hate Auto Layout no longer exist when using Relayout!).
Relayout is also not the definitive implementation of a functional layout. It's an idea, that having a function that generates a list of constraints is a good way to build UI. It's a set of objects that implement that idea. Feel free to take this idea, and build on it, to help make it easier to build and scale UIs.
Finally, Relayout is not a great tool to use in conjunction with Interface Builder. Both Interface Builder and Relayout want you to supply a complete set of constraints to fully describe a UI. Trying to get those two to play nicely is a fight neither you nor I want to solve.
You can install Relayout with Carthage, CocoaPods, or manually.
Add the following to your Podfile
.
pod 'Relayout', '~> 1.0'
Add the following to your Cartfile
.
github "stevestreza/Relayout" ~> 1.0
Relayout.xcodeproj
creates a Relayout.framework, so you can include the framework as an Xcode target dependency, and copy/link it into your app. Alternatively, you can include the source files from the Framework target as you see fit.
When Swift 3 lands the project will be immediately updated to support it with a major update to the version number (e.g. version 2.0.0
).
You can use Relayout from within both views and view controllers. To take advantage of it, you will need a ViewLayout
object. Create one with a root view (such as the view controller's view, the table cell's content view, or the view itself). You will also need to give it an object that conforms to the LayingOut
protocol. This protocol is very simple and has one method which takes your root view and returns an Array<NSLayoutConstraint>
, which represents the list of constraints that you want applied.
Once you have a ViewLayout
object, you'll want to call its layout()
method anywhere that might trigger a layout. Places where that may happen include updateConstraints
, setFrame
, traitCollectionDidChange
, and any place where your UI's state changes.
The simplest way to return constraints is to use the Layout
object, which accepts either an Array<NSLayoutConstraint>
to pass through directly, or a closure that takes your root view and returns the Array<NSLayoutConstraint>
to apply to it.
Relayout was designed to be composable, meaning that the Layout
object is one building block to use to build powerful and flexible layouts. There are a number of implementations of the LayingOut
protocol, either existing or planned. So far you can use:
LayoutGroup
, which returns all of the NSLayoutConstraint
objects provided by an Array<LayingOut>
objectIdentifyingLayout
, which adds an identifier to all NSLayoutConstraint
objects for a given LayingOut
object (which is useful for debugging unsatisfiable constraint errors)ConditionalLayout
, which returns the NSLayoutConstraint
objects from a given LayingOut
object if the condition is true, and optionally return other NSLayoutConstraint
objects if the condition is falseTraitCollectionLayout
, which returns the NSLayoutConstraint
objects from a given LayingOut
object iff the root view has certain UITraitCollection
traitsListLayout
, which iterates over a list of objects, calling a closure that returns NSLayoutConstraint
objects when passed the object, its index, and the previous and next objects in the list (to easily constrain between an object and its next and previous views).And you can of course implement the LayingOut
protocol if you see fit. It has no Self
requirement, so you can use them interchangeably anywhere.