Slide 1

Slide 1 text

Detail-oriented UI with Layout Margins Marina Gornostaeva @hybridcattt Copenhagen Cocoa, 29 May 2019 !1

Slide 2

Slide 2 text

!2

Slide 3

Slide 3 text

!3

Slide 4

Slide 4 text

source !4

Slide 5

Slide 5 text

!5 class BookCell: UITableViewCell { // Layout constants let coverImagePadding: CGFloat = 10 let coverImageHeightPaddings: CGFloat = 30 let titleLabelTopMargin: CGFloat = 15 let labelPadding: CGFloat = -16 let authorTopMargin: CGFloat = 0 let narratorLabelBottomMargin: CGFloat = 15 ... } label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10) label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)

Slide 6

Slide 6 text

!6 func horizontalMargin() -> CGFloat { 
 return IS_IPAD ? 20.0 : 10.0 } let buttonFrame = CGRect(x: 10, y: 10, width: self.width - 10 * 2, height: 44)

Slide 7

Slide 7 text

Layout Margins !7

Slide 8

Slide 8 text

– documentation “ [ Layout margins ] specify the desired amount of space between the edge of the view 
 and any subview ” !8

Slide 9

Slide 9 text

!9 open class UIView {
 
 @available(iOS 8.0, *)
 open var layoutMargins: UIEdgeInsets @available(iOS 11.0, *)
 open var directionalLayoutMargins: NSDirectionalEdgeInsets
 } public struct NSDirectionalEdgeInsets { public var top: CGFloat public var leading: CGFloat public var bottom: CGFloat public var trailing: CGFloat } public struct UIEdgeInsets { public var top: CGFloat public var left: CGFloat public var bottom: CGFloat public var right: CGFloat }

Slide 10

Slide 10 text

Default values !10 view.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) view.layoutMargins.top = 30 view.directionalLayoutMargins.leading = 20 Plain UIView: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) Plain UIViewController ’s view: UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15) UIViewController ’s view in UINavigationController: UIEdgeInsets(top: 8, left: 16/20, bottom: 8, right: 16/20)

Slide 11

Slide 11 text

Manual layout !11 class MyView: UIView { override func layoutMarginsDidChange() { super.layoutMarginsDidChange() self.setNeedsLayout() } override func layoutSubviews() { super.layoutSubviews() // read self.layoutMargins property // for frame calculations } }

Slide 12

Slide 12 text

!12 let lm = self.layoutMargins let buttonFrame = CGRect(x: lm.left, y: lm.top, width: self.width - lm.left - lm.right, height: 44); … myView.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) let buttonFrame = CGRect(x: 10, y: 10, width: self.width - 10 * 2, height: 44)

Slide 13

Slide 13 text

Auto Layout !13 open class UIView { @available(iOS 9.0, *)
 open var layoutMarginsGuide: UILayoutGuide { get } } self.layoutMarginsGuide.leadingAnchor self.layoutMarginsGuide.trailingAnchor self.layoutMarginsGuide.topAnchor self.layoutMarginsGuide.bottomAnchor self.layoutMarginsGuide.width …

Slide 14

Slide 14 text

!14 label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10) label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10) let layoutGuide = self.layoutMarginsGuide subview.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor) subview.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor) subview.topAnchor.constraint(equalTo: layoutGuide.topAnchor) subview.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor) … myView.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

Slide 15

Slide 15 text

Pretty good so far… ✅ Clean code ✅ Native ✅ Easy to change ✅ Reusable !15 !15

Slide 16

Slide 16 text

!16

Slide 17

Slide 17 text

Layout margin propagation View’s layout margins can be increased automatically by UIKit based on: • safe area • margins of all superviews in the view hierarchy !17

Slide 18

Slide 18 text

!18 source

Slide 19

Slide 19 text

!19 view.insetsLayoutMarginsFromSafeArea false true

Slide 20

Slide 20 text

!20 blue.addSubview(red) blue.layoutMargins.left = 60 red.preservesSuperviewLayoutMargins = true

Slide 21

Slide 21 text

preservesSuperviewLayoutMargins + insetsLayoutMarginsFromSafeArea = !21

Slide 22

Slide 22 text

!22

Slide 23

Slide 23 text

!23

Slide 24

Slide 24 text

!24 blue.frame = self.view.bounds red.frame = CGRect(x: 4, y: 90, width: 200, height: 280) red.preservesSuperviewLayoutMargins = true

Slide 25

Slide 25 text

There’s more! !25 open class UIView {
 @available(iOS 9.0, *)
 open var readableWidthGuide: UILayoutGuide
 } open class UIStackView {
 open var isLayoutMarginsRelativeArrangement: Bool
 } open class UITableView {
 @available(iOS 9.0, *)
 open var cellLayoutMarginsFollowReadableWidth: Bool
 }

Slide 26

Slide 26 text

Number-less layout • Use layout margins propagation all the way • Use readableWidth • Use systemSpacing for spacing between views:
 !26 extension NSLayoutXAxisAnchor { open func constraint(equalToSystemSpacingAfter anchor: NSLayoutXAxisAnchor, multiplier: CGFloat) -> NSLayoutConstraint }

Slide 27

Slide 27 text

But…. designers? • educate your designers • change layout margins on the root view of the VC !27

Slide 28

Slide 28 text

Adaptivity app https://itunes.apple.com/dk/app/adaptivity-b/id1054670026 !28

Slide 29

Slide 29 text

!29 That’s it! Start your way to better app UI with UIKit layout margins @hybridcattt