Detail-oriented UI with Layout Margins

Detail-oriented UI with Layout Margins

In iOS 8 Apple introduced layout margins to UIKit, as a way to streamline view alignment easily even for most complex layouts. In this talk I share insights about layout margins and concepts around them.

9b4d0f03e7d36e05eddbc6fbbf1b7fec?s=128

Marina

May 29, 2019
Tweet

Transcript

  1. Detail-oriented UI with Layout Margins Marina Gornostaeva @hybridcattt Copenhagen Cocoa,

    29 May 2019 !1
  2. !2

  3. !3

  4. source !4

  5. !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)
  6. !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)
  7. Layout Margins !7

  8. – documentation “ [ Layout margins ] specify the desired

    amount of space between the edge of the view 
 and any subview ” !8
  9. !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 }
  10. 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)
  11. 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 } }
  12. !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)
  13. 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 …
  14. !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)
  15. Pretty good so far… ✅ Clean code ✅ Native ✅

    Easy to change ✅ Reusable !15 !15
  16. !16

  17. 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
  18. !18 source

  19. !19 view.insetsLayoutMarginsFromSafeArea false true

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

  21. preservesSuperviewLayoutMargins + insetsLayoutMarginsFromSafeArea = !21

  22. !22

  23. !23

  24. !24 blue.frame = self.view.bounds red.frame = CGRect(x: 4, y: 90,

    width: 200, height: 280) red.preservesSuperviewLayoutMargins = true
  25. 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
 }
  26. 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 }
  27. But…. designers? • educate your designers • change layout margins

    on the root view of the VC !27
  28. Adaptivity app https://itunes.apple.com/dk/app/adaptivity-b/id1054670026 !28

  29. !29 That’s it! Start your way to better app UI

    with UIKit layout margins @hybridcattt