Hi Habr!
I want to introduce my latest open-source development - CGLayout
- the second markup system in iOS after Autolayout, based on restrictions.
"The next autoloout system ... why? For what?" - probably you thought.
Indeed, the iOS community has already created quite a few layout libraries, but none have become a truly mass alternative to the manual layout, not to mention Autolayout.
CGLayout
works with abstract entities, which allows you to simultaneously use UIView, CALayer and not rendered
objects for building markup. It also has a single coordinate space, which allows you to build relationships between elements located at different levels of the hierarchy. Able to work in the background stream, easily cached, easily expanded and much, much more.
CGLayout
functional product that has good prospects for developing into a big project.
But initially the goal was commonplace, as usual, just to simplify your life for yourself.
Everyone sometimes has to write a manual layout, either because of the poor Autolayout performance, or because of complex logic. Therefore, some extensions were constantly written (a la setFrameThatFits, etc.)
In the process of implementing such extensions, ideas of a more complex order arise, but due to lack of time, as usual, all this remains within the framework of the recording in Trello and there is an eternity hanging there.
But when you finally got to the realization and you do not see the horizon, it delays you, and it is already unrealistic to stop. Still, I hope the time was not wasted, and my decision will make life easier for someone, if not with the functionality of the framework, then with example code.
In addition to the story of my decision, I will try to analyze and compare other frameworks, so I think it will not be boring.
Requirements | FlexLayout | ASDK (Texture) | LayoutKit | Autolayout | CGLayout |
---|---|---|---|---|---|
Performance | + | + | + | - | + |
Cacheability | + | + | + | + - | + |
Multithreading | - | + | + | - | + |
Cross-hierarchy layout | - | - | - | + | + |
Support for CALayer and 'not rendered' view | - | + | - | - | + |
Extensibility | - | + | + | - | + |
Testability | + | + | + | + | + |
Declarative | + | + | + | + | + |
Some indicators may be subjective, because I have not used these frameworks in production. If I'm wrong, please correct me.
For testing we used LayoutFrameworkBenchmark .
AsyncDisplayKit was not added to the chart due to the fact that it was not enabled by the benchmark developer, and ASDK does layout in the background, which is not entirely fair for performance measurements. Alternatively, see the Pinterest app. The performance is really impressive there.
There are already a lot of information about many frameworks, I will only share my opinion. I will speak mainly about the negative aspects, because they are immediately noticeable, and the advantages are described in the framework pages.
Github 25 issues
Not very flexible, requires writing a large amount of code, and a fairly large inclusion of the programmer in the implementation. I did not find information about using LayoutKit in any other applications besides LinkedIn itself.
Still, it is believed that LayoutKit did not reach its goal, the ribbon in LinkedIn application still slows down.
Features:
Provides the ability to do only layout'om. No other buns, no chips.
Features:
Facebook went by creating a thread-safe abstraction of a higher level. What caused the need to implement the entire stack of UIKit tools. Heavy library, if you decide to use it, then it will be impossible to abandon it. But still this is still the most competent and advanced open-source solution.
Features:
not rendered
objects.Current restrictions:
layer
property leads to undefined behavior, since the frame
in UIView changes implicitly and side effects (such as drawRect, layoutSubviews) are not called. At the same time, the UIView layer
can be used as a restriction for another layer.bounds
value in the superview and there are restrictions based on the superview, it can lead to an unexpected result.What is not yet implemented:
CGLayout
built on the modern principles of the Swift language.
The implementation of markup management in CGLayout is based on three basic protocols: RectBasedLayout
, RectBasedConstraint
, LayoutItem
.
I will call all entities implementing LayoutItem
layout-elements, all other entities are just layout-entities.
public protocol RectBasedLayout { func layout(rect: inout CGRect, in source: CGRect) }
RectBasedLayout
- declares the behavior for changing the markup and defines one method for this, with the possibility of orientation relative to the available space.
The Layout
structure implementing the RectBasedLayout
protocol defines complete and sufficient markup for the layout element, i.e. positioning and dimensions.
Accordingly, the Layout
is divided into two elements: alignment Layout.Alignment
and filling Layout.Filling
. They in turn consist of horizontal and vertical layouts. All components implement RectBasedLayout
. That allows you to use layout elements of different levels of complexity for implementing markup. All entity layouts can easily be extended by your implementations.
All restrictions implement RectBasedConstraint
. If the RectBasedLayout
entities define the markup in the available space, then the RectBasedConstraint
entities RectBasedConstraint
the available space.
public protocol RectBasedConstraint { func constrain(sourceRect: inout CGRect, by rect: CGRect) }
LayoutAnchor
contains specific constraints (side, size, etc.) that have abstracted behavior from the environment.
Currently, the main limiters are implemented.
public protocol LayoutConstraintProtocol: RectBasedConstraint { var isIndependent: Bool { get } func layoutItem(is object: AnyObject) -> Bool func constrainRect(for currentSpace: CGRect, in coordinateSpace: LayoutItem) -> CGRect }
Determine the dependence on the layout element or content (text, picture, etc.). They are self-contained constraints that contain all the information about the source of the constraint and the constraints used.LayoutConstraint
is a constraint associated with a layout element with a specific set of constraints.AdjustLayoutConstraint
- the constraint associated with the layout element contains size-based constraints. Available for layout-elements that support the AdjustableLayoutItem
protocol.
public protocol LayoutItem: class, LayoutCoordinateSpace { var frame: CGRect { get set } var bounds: CGRect { get set } weak var superItem: LayoutItem? { get } }
It is implemented by classes such as UIView, CALayer, and also not rendered
classes. You can also implement other classes, such as stack view.
In the framework, there is an implementation of LayoutGuide. This is an analogue of UILayoutGuide from UIKit, but with the possibility of a layout-element factory. That allows you to use LayoutGuide as a placeholder, which is quite important in light of the latest design solutions. In particular, the ViewPlaceholder class was created for these purposes. It implements the same boot view pattern as a UIViewController. Therefore, working with him will be very familiar.
For elements that can calculate their size, the following protocol is declared:
public protocol AdjustableLayoutItem: LayoutItem { func sizeThatFits(_ size: CGSize) -> CGSize }
By default, it is implemented only by UIView.
public protocol LayoutCoordinateSpace { func convert(point: CGPoint, to item: LayoutItem) -> CGPoint func convert(point: CGPoint, from item: LayoutItem) -> CGPoint func convert(rect: CGRect, to item: LayoutItem) -> CGRect func convert(rect: CGRect, from item: LayoutItem) -> CGRect var bounds: CGRect { get } var frame: CGRect { get } }
The layout system has an integrated coordinate system represented in the form of the LayoutCoordinateSpace
protocol.
It creates a single interface for all layout-elements, while using the basic implementations of each type (UIView, CALayer, UICoordinateSpace + own implementation for cross-conversion).
public protocol LayoutBlockProtocol { var currentSnapshot: LayoutSnapshotProtocol { get } func layout() func snapshot(for sourceRect: CGRect) -> LayoutSnapshotProtocol func apply(snapshot: LayoutSnapshotProtocol) }
Layout-block is a complete and independent unit of the layout. It defines methods for performing markup, getting / applying snapshot.
LayoutBlock
encapsulates a layout element, its main layout and constraints that implement the LayoutConstraintProtocol.
The process of updating markup begins with determining the available space using constraints. It should be borne in mind that the system still solves problems with conflict restrictions and does not prioritize them at all, therefore, one should carefully consider the application of restrictions. So in general, size-based restrictions (AdjustLayoutConstraint) should be set after position-based restrictions. The source space is the super space (bounds). Each constraint changes the available space (cuts, displaces, stretches, etc.). After the constraints have completed, the resulting space is transferred to the Layout
, where the actual markup for the element is calculated.
LayoutScheme
is a block that combines other layout blocks and determines the correct sequence to perform the markup.
public protocol LayoutSnapshotProtocol { var snapshotFrame: CGRect { get } var childSnapshots: [LayoutSnapshotProtocol] { get } }
LayoutSnapshot
is a snapshot presented as a set of frames, preserving the hierarchy of layout elements.
All extensible elements implement the Extended protocol.
public protocol Extended { associatedtype Conformed static func build(_ base: Conformed) -> Self }
Thus, when extending the functionality, you can use the type already defined in CGLayout
to build a strongly typed interface.
CGLayout is almost the same as manual layout in the sense of building a sequence of frame actualization. When implementing the markup in CGLayout, the programmer must remember that all restrictions must start with actual frames before starting work, otherwise the result will be unexpected.
let leftLimit = LayoutAnchor.Left.limit(on: .outer) let topLimit = LayoutAnchor.Top.limit(on: .inner) let heightEqual = LayoutAnchor.Size.height() ... let layoutScheme = LayoutScheme(blocks: [ distanceLabel.layoutBlock(with: Layout(x: .center(), y: .bottom(50), width: .fixed(70), height: .fixed(30))), separator1Layer.layoutBlock(with: Layout(alignment: separator1Align, filling: separatorSize), constraints: [distanceLabel.layoutConstraint(for: [leftLimit, topLimit, heightEqual])]) ... ]) ... override public func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() layoutScheme.layout() }
In this example, the separator uses distanceLabel
restrictions after the position of this label is determined.
Autolayout is still the main layout tool due to its stability, good API and powerful support. But third-party solutions can help solve particular problems, because of their narrow focus or flexibility.
CGLayout
has a not quite familiar logic for describing the layout process, and therefore requires getting used to.
There is still a lot of work to do, but it is a matter of time, while it is already clear that it has a number of advantages that should allow it to occupy its niche in the field of similar systems. The framework has not yet been tested in production, and you have the opportunity to try it out. The framework is covered with tests, so big problems should not arise.
I hope for your active participation in the further development of the framework.
And in the end I would like to ask the habra users:
What are your requirements for layout-systems?
What do you don’t like most when building your layout?
Source: https://habr.com/ru/post/338540/
All Articles