Skip to content

Rightpoint/Stackable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stackable

Stackable is a delightful and declarative set of utilities for UIStackView. It is designed to make your layout code easier to write, read, and communicate with your designer.

Version License Platform

Stackable aims to bridge the gap between the way designers articulate layout and the way developers express that layout in code.

stack.stackable.add([
    logo,
    30,
    "Example Views",
    10,
    cells
        .outset(to: view)
        .margins(alignedWith: stack),
    UIStackView.stackable.hairlines(around: cells)
        .outset(to: view),
    20...,
    "Copyright Rightpoint",
])

Views

Stackable includes built-in support for all UIView subclasses.

Add them using stackView.stackable.add(_ stackables: [Stackable])

stack.stackable.add([
    imageView,
    label,
    button,
])

Additionally, you can reference UIViewController directly to include its view.

stack.stackable.add([
    viewController,
    button,
])

There is also support for:

  • String
  • NSAttributedString
  • UIImage
stack.stackable.add([
    "String",
    NSAttributedString(string: "Attributed String"),
    UIImage(named: "example"),
])

Alignment

Views can be expressively manipulated to adjust their alignment in a UIStackView.

let stack = UIStackView()
stack.axis = .vertical
stack.stackable.add([
    imageView
      .inset(by: .init(top: 20, left: 20, bottom: 20, right: 20), //imageView padded on all sides 20px
    
    label
      .inset(by: .init(top: 0, left: -20, bottom: 0, right: -20) //inset can also be negative to move outside of bounds of `stack`
      .aligned(.right), // aligned right (transforms are composable)
      
    button
      .aligned(.centerX), // button aligned center horizontally
      
    hairline
      .outset(to: view), // hairline pinned to horizontal edges of `view`
      
    cells
      .outset(to:  view) // cells horizontal edges pinned to horizontal edges of `view`
      .margins(alignedWith: view), // cell layout margins updated to line up with view.layoutMarginsGuide
])

Spaces

Spaces can be added using a number literal or StackableSpaceItem. Add them alongside your views.

Fixed Spaces

stack.stackable.add([
    20, //constant space
    viewA,
    20, //custom spacing after `viewA`, will be removed if viewA is hidden.
    viewB,
    12.5, //floating point works too!
    viewC,
    UIStackView.stackable.constantSpace(20), //Constant space, not dependent on visibility of `viewC`
    viewD,
    UIStackView.stackable.space(after: viewD, 20), //Explicit custom space dependent on visibility of viewD. Equivalent to just using `20` here.
    viewE,
    UIStackView.stackable.space(before: viewF, 20), //Custom space before viewF. Dependent on the visibility of `viewF`
    viewF,
])

Flexible Spaces

stack.stackable.add([
    viewA,
    20...30, // Flexible space, at least 20, at most 30, inclusive. Flexible spaces do not track view visibility.
    viewB,
    20..., // Flexible space. At least 20, no maximum.
    viewC,
    ...15.5, // Flexible space. At least 0, at most 15.5, floating point works too!
    viewD,
    UIStackView.stackable.flexibleSpace, // flexible space, at least 0, no maximum. Equivalent to `0...`
    viewE,
    UIStackView.stackable.flexibleSpace(.atLeast(20)), // flexible space, at least 20, no maximum. equivalent to `20...`
    viewF,
    UIStackView.stackable.flexibleSpace(.atMost(20)), // flexible space. at least 0, at most 20, equivalent to `...20`
    viewG,
    UIStackView.stackable.flexibleSpace(.range(10...20)), // flexible space, at least 10, at most 20, equivalent to `10...20`
])

Advanced Spaces

let cells: [UIView] = ...
stack.stackable.add([
    cells,
    UIStackView.stackable.space(afterGroup: cells, 20), // A space that is dependent on at least one view in `cells` to be visible.
])

Hairlines

stack.stackable.add([
    viewA,
    UIStackView.stackable.hairline, // Simple hairline, extended to edges of StackView. 
    viewB,
    UIStackView.stackable.hairline(after: viewB), // hairline that mirrors visibility of `viewB`
    viewC,
    UIStackView.stackable.hairline(between: viewC, viewD), // hairline that is dependent on both `viewC` and `viewD` being visible.
    viewD,
    UIStackView.stackable.hairline(before: viewE), // hairline before `viewE`, mirrors visibility of `viewE`
    viewE,
    viewF,
    UIStackView.stackable.hairlines(around: viewF), // hairline added above and below viewF. Mirrors visibility of `viewF`
])

Hairlines and Groups

let cells: [UIView] = ...

stack.stackable.add([
    cellsA,
    UIStackView.stackable.hairlines(between: cellsA), // Hairlines between any visibile members of `cellsA`

    cellsB,
    UIStackView.stackable.hairlines(after: cellsB), // Hairlines after all visible members of `cellsB`

    cellsC,
    UIStackView.stackable.hairlines(around: cellsC), // Hairlines above and below all visible members of `cellsC`
])

Hairline Alignment

stack.stackable.add([
    viewA,
    UIStackView.hairline
        .inset(by: .init(top: 0, left: 10, bottom: 0, right: 10) // hairline inset horizontally by 10
      
    viewB,
    UIStackView.hairline
        .inset(by: .init(top: 0, left: -10, bottom: 0, right: -10) // hairline outset horizontally by 10

    viewC,
    UIStackView.hairline
        .outset(to: view) // hairline constrained to edges of some ancestor (horizontal edges for a vertical stack)
])

Hairline Appearance

Hairline appearance can be customized on a per hairline or group basis:

stack.stackable.add([
  viewA,
  UIStackView.hairline
      .color(.lightGray)
      .thickness(1),
    
  cells,
  UIStackView.hairlines(around: cells)
      .color(.red)
      .thickness(10)
])

It can also be customized on a per UIStackView basis:

let stack = UIStackView()
stack.stackable.hairlineColor = .lightGray
stack.stackable.hairlineThickness = 1

It can also be customized with a global default:

UIStackView.stackable.hairlineColor = .lightGray
UIStackView.stackable.hairlineThickness = 1

If more customiztion is required, you may provide a HairlineProvider to vend the UIView that should be used as a hairline:

let stack = UIStackView()
stack.stackable.hairlineProvider = { stackView in
    let customHairline = UIView()
    //...
    return customHairline
}

// global default provider
UIStackView.stackable.hairlineProvider = { stackView in
    let customHairline = UIView()
    //...
    return customHairline
}

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

  • iOS 12.0+

Installation

Stackable is available as a Swift Package. You can also install it through CocoaPods by adding the following line to your Podfile:

pod 'RPStackable'

Author

jclark@rightpoint.com

License

Stackable is available under the MIT license. See the LICENSE file for more info.