قالب وردپرس درنا توس
Home / IOS Development / A swift wrapper around automatic layout

A swift wrapper around automatic layout



Cocoa Auto Layout is an effective limitation system, but as an "layout" API, it leaves much to be desired. In most respects, Auto Layout is a limitation API as we can build a layout system, but is not an API that offers layout.

In this article, I'll look at why I'm using Auto Setup directly can be frustrating or limiting before showing my own CwlLayout code that is a thin layer over Auto Layout (for either iOS or macOS) that I use to make the setup less laborious and minor error issues.

You can download this Article as a Swift Playground from Github.

Declarative Programming

Declarative programming is about describing an application as a set of rules and relationships. Syntactic, declarative programming often involves gathering an entire system of rules as either a simple expression or domain-specific language whose structure reflects the relationship between the rules in the system.

Layout is an inherent declaratory task. Layout is a set of rules (which Auto Layout calls "Restrictions") that apply to the content of a view, ideal for the entire life of the content. Limitation of programming itself is sometimes considered a subdisciplinary for declarative programming.

Unfortunately, despite Auto Layout Modeling, a declarative system, it does not offer a highly declarative API. There are a handful of ways to use Auto Layout, but no one can be written as a single phrase, most require a variety of mutable property changes, and in many cases, multiple restrictions may be used at separate times.

The end result is unesthetical code that is incompatible by default. In fact, since each view requires 4 separate constraints (width, height, x-position and y-position), any arbitrary set of Auto Layout constraints is far more likely to be inconsistent than consistent ̵

1; and an inconsistent system of rules is not something system at all.

An example layout

Let's look at a "simple" layout example to see what I mean. All examples in this article design the following layout:

 Figure 1: A single layout with two bottom-right labels

Figure 1: A single layout with two bottom-right labels

] This layout contains two UILabel s and the gray area are the boundary of the container UIView .

The layout adjusts the bottom edge of the two UILabel s, both labels are the same width and place both in the upper view so that the highest touches the upper edge of the display, the left and right labels touch the left and right edges , the labels are horizontally separated with a multipurpose and the bottom of the view is free.

A key point to note this setting is that it does not tag the height of the label. If the text changes, the labels will adjust the heights, change any label that touches the top margin and possibly change how much of the view is available at the bottom, but they would maintain their equal widths and maintain their adjusted bottom edge.

UILabel UILabel : UILabel 19659024]) {
left . translatesAutoresizingMaskIntoConstraints = false
correct . translatesAutoresizingMaskIntoConstraints = false
display . addSubview [19659025] ( stands )
display . addSubview [19659025] ( correct )

la limitations = [
stands . leadingAnchor . restriction ( equalTo : view layoutMarginsGuide leadingAnchor ..)
correct ] [ ] . . View LayoutMarginsGuide TrailingAnchor )., [19659055] bottomAnchor Limitation ( equalTo . Constant 19659097] – 8 ),
stands . topAnchor . restriction greaterThanOrEqualTo .:. view layoutMarginsGuide topAnchor ).,
correct 19659020] topAnchor restriction ( greaterThanOrEqualTo .:. view layoutMarginsGuide topAnchor ).,
] width restriction ( equalTo . correct widthAnchor ). ] [19659049] NSLayoutConstraint . activate ( limitations )

la leftTop = stands . topAnchor . restriction ( equalTo .:. view layoutMarginsGuide topAnchor ).
leftTop [19659026] priority = LayoutDimenion . PriorityDefaultLow
leftTop . isActive = true
la rightTop = right . topAnchor restriction ( equalTo .:. view layoutMarginsGuide topAnchor ).
rightTop . Priority = LayoutDimension . PriorityDefaultLow
rightTop . isActive = true
} [19659021] la view1 = runExample ( reverses : false example : example1 )

NOTE: If you see this on Swift Playground, the last light view1 = line will get a gray " Show Result "field in the right margin. Click this square to view the layout. Set reverse parameter to true to swap the labels in the text fields.

The clear problem here is the fast volume of code. I've had to write nine separate constraints, put a handful of features and other actions, including adding tutorials and activating restrictions. It is a party of unforgiving, thorough work. Deletion of any line will not only change the layout, but will probably leave the setup in one of the following four problematic states:

  1. inconsistent (no single solution satisfying all limitations)
  2. ambiguous (more than one solution satisfying all constraints)
  3. overlapping (views partially or completely occupied in the same space, inadvertently)
  4. crowded (views that exceed the parent borders inadvertently)

These issues will not occur as compilation time errors that clearly say "fix this line", They are runtime errors that can give confusing or difficult to understand results. Even though you get information in the console about reporting "ambiguous layout that affects display height [x]", it may be very difficult to return to the root of limitations and find out where you've gone wrong.

Limitations are hard.

Brain teaser : one (and only) of the 9 limitations can be removed without leaving the setup in one of the four problematic states listed above. Can you guess any?

Much of the debate I'm looking around Auto Layout focuses on how cumbersome it is to implement in code (as opposed to Interface Builder), but all of these 9 restrictions will still be used in Interface Builder to achieve this setup and you will still need to understand why each of the limitations is necessary.

Interface Builder can provide more instant feedback when the layout is ambiguous or inconsistent, but it does not change the number of constraints you need to create or need to guess, from messages like "ambiguous layout that affects the viewing height [x]" that you can have made a mistake. You need a deep understanding of the limitation system to avoid abuse, and while it's not rocket science, it can still be confusing.

Pseudo Layouts

The problems with manually used restrictions derive from views requiring at least 4 constraints at any time (width, height, x-position, y-position), but the API applies to 1 rule at a time. To ensure that the rule set is consistent requires knowledge of the desired end effect. Columns? Rader?

The purpose of this feature is to provide better visualization of constraints, but it has a much greater advantage: geometry. The restrictions (withVisualFormat: ... partially work models a column or line so that it can determine what additional restrictions may be required to ensure alignment in the column or line and ordering of goods : ]: UILabel rett . UILabel [19659024]) {
stands translatesAutoresizingMaskIntoConstraints = false
[riktige19659025]. translatesAutoresizingMaskIntoConstraints = false [19659049] riss . addSubview ( stands )
display . )
la view = [ "left" : left [right] : right ] [19659307] la constraints1 = NSLayoutConstraint Restrictions ( .
– [right(==left)] – | the options . alignAllBottom calculations : nil views : [19659028] views ]

la constraints2 = NSLayoutConstraint Restrictions ( withVisualFormat :
"V:. | – (> = 8) – [left]" alternatives : [] calculations ]: nil [1965902 ]

la constraints3 = NSLayoutConstraint Restrictions ( withVisualFormat . [V:|-(>=8)-[right] " Alternatives : [] Calculations : nil [19659025] data : views )
NSLayoutConstraint activate ( constraints1 )
NSLayoutConstraint . ] activate ( constraints2 )
NSLayoutConstraint . activate ( constraints3 )
}
la [19659062] view2 = runExample ( reverses : ] false example : middle name2 )

It is much shorter, but it is still uncertain. While impossible constraints are less common, ambiguous layouts are still possible because modeling is primarily 1-dimensional, but constraints must agree in two dimensions.

There are also many esoteric requirements such as "greater or equal" vertical distance limitation that can easily be misapplied and creates problems in this type of layout.

An Actual Layout

Regardless of how to use them, limitations are not layouts; Limitations are building blocks from where we can make create layouts. An actual layout requires that you start another way.

UIKit and AppKit provide UIStackView / NSStackView . Let's have a look:

  func    example3  (  see :    UIView    left :    UILabel  [19659030] correct [19659024]:    UILabel )    {

rad = UIStackView ( arrangedSubviews : [ ] stands correct ]) p . alignment = . bottom p . space = 8 la topAlignedBox = UIStackView ( arrangedSubviews : [ p [19659025] ]]) topAlignedBox . alignment = . top topAlignedBox . autoresizingMask = [[19659026] flexibleWidth . flexibleHeight ] topAlignedBox . frame [1 9659038] = display . boundaries topAlignedBox . isLayoutMarginsRelativeArangement = sanne topAlignedBox . preservesSuperviewLayoutMargins = true display . addSubview ( topAlignedBox ) } view3 = runExample [19659025] : false example : example3 )

Using a UIStackView is far better than raw constraints. UIStackView distributes views along its axis – which happens to the event I've used in this article – and most properties in the stack view can easily be omitted and the layout will not be ambiguous, inconsistent, overlapping, or crowded.

However, there are some issues:

  1. There are still properties whose configuration can not be omitted
  2. Stackview can not be configured in a single expression
  3. Stack view is relatively inflexible

On the first issue: you must be correct place the outer stack display in the display or nothing will be displayed. Clear the frame frame to see what's happening. autoresizingMask can also be problematic if the container display needs to resize.

On the second problem: You can see in the example that I had to nest a stack view in another to get the correct layout. Nested layers are not a problem, but since the stacking views can not be fully configured in a simple expression, implementation becomes increasingly messy, as we must assign all stack views to variables, configure them, and finally put them together.

Third Point: It can be difficult to control the width of items in a Stack View row or heights in a Stack View column if you do not use to add only limitations (the situation we are trying to avoid). UIStackView scales based on compression resistance and clamp priority, but I could not use these values ​​to adjust the size of the child.

CwlLayout

UIStackView / NSStackView provides a good conceptual model for layout, but their implementation is missing.

A declarative approach – where the whole system of constraints is established in a single phrase – would be much better. Fortunately, Swift has a number of syntactic benefits that can help us here. Swift has:

  • enums with related types
  • dots of static members on a type
  • typesafe variable argument lists
  • default arguments parameters

By using these we can write code that is close to [ : UIView stands : UILabel correct : UILabel ) {
display applyLayout [19659025] (
horizontal ( adjust .: .. leading
horizontal ( adjust . following
matchedPair ( View ), View ( Correct ))
) [19659551]) ) ) 9130]]
} la view4 = runExample ( reversed : false example : example4 )

Conceptually uses this same approach as the previous UIStackView example: We have a row containing a few views directed at the bottom edge in another box toward the top ( The adjustment is perpendicular to the layout direction so that adjust the parameters .leading and .trailing interpreted as "vertically leading" ie top and "vertical trailing" – ie bottom).

Compared to UIStackView this example is more syntactically effective, has a visual structure that reflects its composition structure, is a simple expression and inherently consistent.

The last point may be the most important thing: you can remove sub looks completely out of the hierarchy, but there's nothing you can remove that leaves the layout in an inconsistent, ambiguous, overlapping, or crowded state. Do not misunderstand me: you can still add inconsistent limitations with CwlLayout, but you must take positive action for Introducing Inconsistency – The system is consistent by default.

You should note that there is no need to add captions or configure other properties on the views like translateAutoresizingMaskIntoConstraints . Everything is handled automatically. Additionally, a subsequent call to applyLayout (including applyLayout (nil) ) will accurately remove any previously used layout, restrictions used by the setup, and any additional lessons (leaving views and restrictions added to otherwise untouched). Everything is done in a single operation.

Some variations

Custom size restrictions

I mentioned that one of the disadvantages of UIStackView is that it can be difficult to control the size of the elements contained. The stack display uses its own dimension logic that is opaque and difficult to override.

For example, think instead of this 50 / 50 split, we wanted a split 75 / 25 . With CwlLayout, it's easy to use a sizeView (one that includes explicit size restrictions):

  func    example5  (  view :    UIView  ]           .    UILabel )    {
     ]   horizontal    adjust .: ..      leading  
            horizontal  ([19659026] adjust . [19659020]             :      Equal To  19659039]     Constant :    -   0.75    *    8 ),    left [19659025]), 
             ].   space  (), 
            .   show ]   ] 
         ) 
      ) [19659162]) 
} 
  la    view5    =    runExample  ([19659022]  :    false     example :    example5 ) 

Ratio 0.75 is a relationship between the parent container – that is to say The inner . Horizontal layout, as the outer layout already has input of the display margins. However, we will also accommodate 8 pixels between the two labels, hence the -0,75 * 8 constant drawn from the width, giving a perfect ratio of 3: 1 between the two labels.

"8-place" was created by .space () corresponding .space (8.0) . CwlLayout included a .space () automatically between matchedPair in the previous example, but since we manually manage the row more manually this time, it is explicitly included. This 8-unit separation is a standard room that Apple proposes for margins and spaces between adjacent views.

Please note, however, that UIViewController has very different margins and safeAreaGuides in iOS 11 is different. CwlLayout respects margins by default on the outer layout bin, and it is a parameter marginEdges (hidden in this example) that allows you to switch between each border between spaces, layout margins and no margins.

It is also possible to specify width (size perpendicular to setup direction). If the width is specified as a relationship, there may be a relationship between the parent container or a ratio of length so that the size ratio with preservation of the size is possible.

Vertical Location Management

The two previous examples have used nested .horisontal rows (because that's what I used in the UIStackView example) but I could equally easily change the outer container of a column and begin to control vertical placement:

  func    example6  [  view :    UIView    left :    UILabel [19659024] [19659030] correct .:.  UILabel )    {
     display    applyLayout  (
         Vertical  (
         .   space    equal to  (  relationship .    0.15 )., 
            horizontal  ([19659026] adjust [19659025]: .. .      following  
               matchedPair  (
                  riss  (  stands ), [19659735].   display  (  correct ) 
            ) 
         ), 
         .   space  (.   fillRemaining ) [19659551]) 
} 
  la    view6    =    runExample  ([19659020]:   :    false     example :    example6 ) 

Use

The CwlLayout.swift file is available in the Swider sources folder on Swift Playground for this article. The file has no addictions beyond AppKit / UIKit, so you can only drop it into any of your projects.

CwlLayout requires Swift 4.2 and a distribution target for MacOS 10.13 or iOS 11 or higher. Older versions that support iOS 10, MacOS 10.12 and Swift 3.2 are in the git story.

The file is missing full test coverage, so there may be some rough edges. If you encounter any obvious mistake, let me know.

CwLLayout supports animation when moving from one layout to another. Maybe I'll document it in the future.

If you look at the code, you will notice that there is a DEBUG setting inside. For many tasks, CwlLayout uses UILayoutGuide / NSLayoutGuide to define regions, but a compiled state will replace these instructions for UIView / NSView in The place since views appears better in the Xcode troubleshooting tools.

Conclusion

Using Auto Layout Restrictions directly is an exercise in frustration. They are inconsistent by default, and the errors will not be captured by the compiler, so driving problems are a continuous issue. The fact that the code is often unaesthetic and incompatible adds to the frustration.

Apple's documentation and videos focus on using Auto Layout in Interface Builder, which avoids the aesthetics of the code, but the fact is that even in the Interface Builder, still only the same independent restrictions apply and it is still prone to being inconsistent if You are not precise and accurate.

A far better solution for programming a declarative constraint system is to use an API that itself is declarative and builds the entire system as a simple composite expression and instead of constructing individual constraints separately. An approach that understands the entire system and can automatically contain the constraints needed will also contribute to consistency.

A UIStackView or NSStackView becomes part of the road to this ideal. These classes understand the overall layout and limit the amount of inconsistency, but they are relatively inflexible and usually require more sentences to configure.

CwlLayout is not complicated or revolutionary; It's just a more flexible column and row approach like UIStackView or NSStackView . Unlike UIStackView or NSStackView however, it is made exclusively for Swift and utilizes Swift's syntactic efficiency for a more aesthetic design.

Looking forward to …

Layout is just the top of the glacier.

Almost all app development – windows, views, controls, buttons, actions, fonts – can be handled as a set of rules. Some rules determine mapping between model and display, and some dictate how events and user actions should be managed and handled.

How would a declarative approach for an entire app look like?


Source link