There is only one thing in the world, which is more painful than using SwiftUI. It is going back to UIKit after getting used to SwiftUI.

To decrease that amount of pain, I've made SwiftUIKit.

SwiftyUIKit is a featherweight lib that allows building an auto layout-powered UIView layout in a clean SwiftUI-style while using pure UIKit API for views configuration and additional layout tweaking.

3rd party libs around UIKit

I'm not a fan of using monster-sized 3rd party libs for building UI layout.

Wrapping UIKit is always a trade-off between minimalism and flexibility.

Layout helper libraries, like SnapKit, are incredible, but what they usually do is just turning UIKit's auto layout API, into another API, perhaps with a little bit more attractive look.

However, mapping a complex API into another complex API actually doesn't change the game. A complicated layout still requires complicated code. Possibly a little bit more readable thanks to syntax sugar.

No doubt, code readability is important, especially taking into account the auto layout API, which is sometimes pretty much verbose, to be honest.

But this time readability is obtained by creating more and more APIs to the same framework. We simply build our own UIKit Tower of Babel.

In my opinion, it looks suboptimal that's why I try to be as close to UIKit API as possible and play with NSLayoutConstraints directly.

Introducing SwiftyUIKit

Here comes SwiftyUIKit consisting of just 4 views, 1 decorator, and 1 closure that does all the work and makes the layout code look clean as if it is SwiftUI.

Instead of creating another API, SwiftyUIKit is a kind of mix of SwiftUI and UIKit.

How does the layout look like with SwiftUIKit? Here is an example

final class ExampleView: UIView {
    private let theme: AppUITheme = .defaultTheme
    private let titleLabel = UILabel(frame: .zero)
    private let subtitleLabel = UILabel(frame: .zero)
    private let imageView = UIImageView(frame: .zero)

    private lazy var body = {
        HStackView(spacing: theme.paddings.normal) {
            VStackView {
                imageView.modify {
                    $0.contentMode = .scaleAspectFill
                    $0.widthAnchor
                        .constraint(equalTo: $0.heightAnchor, multiplier: 1.0)
                        .isActive = true
                    $0.heightAnchor
                        .constraint(equalToConstant: theme.sizes.avatar)
                        .isActive = true
                }

                SpacerView()
            }

            VStackView(spacing: theme.paddings.small) {
                titleLabel.modify {
                    $0.numberOfLines = 0
                }
                subtitleLabel.modify {
                    $0.numberOfLines = 0
                }
            }
        }
        .modify {
            $0.heightAnchor
                .constraint(greaterThanOrEqualToConstant: theme.sizes.avatar)
                .isActive = true
        }
        .padding(bottom: theme.paddings.large)
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        add(body: body)
    }
}

What's inside

The whole lib consist of just a few guys:

  • HStackView
  • VStackView
  • ZStackView
  • SpacerView
  • Padding view decorator
  • Add as body function
  • Modify closure

HStackView, VStackView, ZStackView

HStackView, VStackView simply arranges its subviews in stacks statically using layout constraints. It just anchors subviews' edges to each other.

ZStackView arranges its subviews allowing options for anchoring edges, like in SwiftUI.

As we have experienced in SwiftUI, it's usually enough for 90% of the layouts.

Why not just UIStackView?

Once, I've measured UIStackView performance and was very surprised by how slow it really is.

What if I want a lightweight layout with views just stacked statically without rearrangement and any other overhead brought by UIStackView?

SpacerView

SpacerView is just a view with low hugging priorities, so when it is used in a stack along with a label, it pushes the label similarly to how it happens in SwiftUI.

However, it may sometimes require additional constraints, to layout properly.

Paddings Decorator

Decorates view with paddings all over:


UILabel().padding(16)

or with specified sides:


UILabel().padding(left: 8, right: 8)

Add body:

func add(body: UIView) 

It just adds body view as a subview with its edges anchored to the parent view's edges.

Modifier sugar

It's a closure to make the layout and view description look more expressive, compact, and neat while using the exact same UIKit's APIs

It can be used for

  • UIView
  • NSLayoutConstraints
  • NSLayoutAnchors
  • Arrays consisting of UIView,  NSLayoutConstraints, NSLayoutAnchors

UIViews

UIView().modify {
    $0.backgroundColor = .red
}

NSLayoutConstraints and NSLayoutAnchors

UIView()
    .leftAnchor
    .constraint(equalTo: leftAnchor)
    .modify {
        $0.priority = .defaultHigh
        $0.isActive = true
    }

Arrays of all the modifiable guys mentioned above

UIImageView().modify { 
        [$0.heightAnchor, $0.widthAnchor].modify { anchor in
                anchor.constraint(equalToConstant: 32)
                           .isActive = true
    }
}

or

[titleLabel, subtitleLabel].modify {
    $0.font = veryNiceFont
}

That's it!

Installation

Swift Package Manager.

SwiftyUIKit is available through Swift Package Manager.
To install it, in Xcode 11.0 or later select File > Swift Packages > Add Package Dependency... and add SwiftyUIKit repository URL:

https://github.com/KazaiMazai/SwiftyUIKit