Swiftpack.co - Package - MihaelIsaev/UIKitPlus

MIT License Swift 5.1 Cocoapod

This project is in active development state

With that lib you could build UI in SwiftUI-like way for iOS9 and higher! ๐Ÿ˜บ

// NOTE:
// Now it is written for Swift 5.1 and Xcode 11
// stable code for Swift 4.2 is available in swift4 branch

Support this lib by giving a โญ๏ธ!


โš ๏ธ don't use Xcode 11.2 and 11.3 because of its compiler bug which is showing Type of expression is ambiguous without more context in function builders if expression is long while the same code compiles just fine with simple swift build in console. Use Xcode 11.1 cause there everything compiles fine. Hope this issue will be fixed(please vote for this issue here).

Xcode 11.1 available here https://developer.apple.com/download/more/

If you experience any compilation problems in Xcode 11.1 please go to Xcode -> Preferences -> Location open derived data folder in finder and remove everything from it manually. Then restart Xcode and compile your project without any errors.

Learn how to use through the Example project ๐ŸŽฎ

Please feel free to request for specific examples in example project issues I'll try to cover as much as I can ๐Ÿš€

Main Features

You can build your view anywhere with all the constraints (even to other views), and then once you add it into a superview all the constraints will be activated.

Reusing views is pretty easy. Just declare them in extensions!

Really short intro

class MyViewController: ViewController {
    lazy var view1 = View()
    lazy var view2 = View()

    override func buildUI() {
        body {
            view2.background(.red).size(30, 20).centerXInSuperview().top(to: .bottom, of: view1, 16)

Long intro

import UIKit
import UIKitPlus

// Just feel how easy you could build & declare your views
// with all needed constraints, properties and actions
// even before adding them to superview!
class LoginViewController: ViewController {
    @State var email = ""
    @State var password = ""
    override func buildUI() {
        view.backgroundColor = .black
        body {
            Button.back.onTapGesture { print("back tapped") }
            VStack {
                View().height(10) // just to add extra space
                Button.bigBottomGreen.title("Sign In").tapAction(signIn)
            }.edgesToSuperview(top: 120, leading: 16, trailing: -16)

    func signIn() {
        // do an API call to your server with my awesome CodyFire lib ๐Ÿ˜‰

And that's it! Yeah! You just need few extensions to make it work ๐Ÿ˜

// To avoid mess declare reusable views in extensions like this
extension FontIdentifier {
    static var sfProRegular = FontIdentifier("SFProDisplay-Regular")
    static var sfProMedium = FontIdentifier("SFProDisplay-Medium")
extension Text {
    static var title: Text { Text().color(.white).font(.sfProMedium, 18) }
extension TextField {
    static var welcome: TextField {
            .border(.bottom, 1, .gray)
            .font(.sfProRegular, 16)
extension Button {
    static var back: Button { return Button("backIcon").topToSuperview(64).leadingToSuperview(24) }
    static var bigBottomGreen: Button {
            .font(.sfProMedium, 15)
            .shadow(.gray, opacity: 1, offset: .init(width: 0, height: -1), radius: 10)

// PRO-TIP2:
// I'd suggest you to use extensions for everything: fonts, images, labels, buttons, colors, etc.


With CocoaPods

Add the following line to your Podfile:

pod 'UIKit-Plus', '~> 1.9.0'

With Swift Package Manager

In Xcode11 go to File -> Swift Packages -> Add Package Dependency and enter there URL of this repo


With Carthage

Not supported yet. Feel free to send PR for that.


import UIKit
import UIKitPlus


| | UIKitPlus | UIKit | | - | ------- | -------------- | | โœ… | View | UIView | | โœ… | WrapperView | UIView | | โœ… | ScrollView | UIScrollView | | โœ… | CollectionView, Collection | UICollectionView | | โœ… | TableView, List, StaticList | UITableView | | โœ… | Image | UIImageView | | โœ… | Button | UIButton | | โœ… | Text | UILabel | | โœ… | TextField | UITextField | | โœ… | SegmentedControl | UISegmentedControl | | โœ… | VisualEffectView | UIVisualEffectView | | โœ… | StackView | UIStackView | | โœ… | HStack | UIStackView | | โœ… | VStack | UIStackView | | โœ… | VerificationCodeView | | | โœ… | AttributedString (aka AttrStr) | NSAttributedString | | โœ… | ViewController | UIViewController | | โœ… | NavigationController | UINavigationController | | โœ… | FormController | | | โœ… | DatePicker | UIDatePicker | | โœ… | Stepper | UIStepper | | โœ… | Slider | UISlider | | โœ… | Toggle | UISwitch |

โœ‹ Readme below should be updated, cause there are a lot of new features in v1.0.0 and higher


It is just a simple view with ability to customize it declarative way


Also you can initialize it with predefined subviews

View.subviews {
    let avatar = Image("some").size(100)
//                             stick to top, leading and trailing of superview
                              .edgesToSuperview(top: 0, leading: 0, trailing: 0)

//                                 stick top to bottom of avatar view with 16pt
    let name = Label("John Smith").top(to: .bottom, of: avatar, 8)
//                                 stick to leading, trailing and bottom of superview
                                  .edgesToSuperview(leading: 0, trailing: 0, bottom: 0)
    return [avatar, name]


It is simple View but with ability to initialize with inner view

WrapperView {

and you could specify innerView`s padding right here

// to the same padding for all sides
WrapperView {
// or to specific padding for each side
WrapperView {
}.padding(top: 10, left: 5, right: 10, bottom: 5)
// or even like this
WrapperView {
}.padding(top: 10, right: 10)


Nothing interesting yet, but you could specify some settings in declarative manner

ScrollView().contentInset(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().contentInset(top: 10, bottom: 10)
ScrollView().scrollIndicatorInsets(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().scrollIndicatorInsets(top: 10, bottom: 10)


Nothing interesting yet



Nothing interesting yet




Button("Tap me")
Button().title("Tap me") // useful if you declared Button from extension like below
Button.mySuperButton.title("Tap me")

background and background for highlighted state

Button("Tap me").background(.white).backgroundHighlighted(.darkGray)

title color for different states

Button("Tap me").color(.black).color(.lightGray, .disabled)

set some font from declared identifiers or with system fonts

Button("Tap me").font(v: .systemFont(ofSize: 15))
Button("Tap me").font(.sfProBold, 15)

add image

Button("Tap me").image(UIImage(named: "cat"))
Button("Tap me").image("cat")

You can handle tap action easily

Button("Tap me").tapAction { print("button tapped") }
Button("Tap me").tapAction { button in
    print("button tapped")

or like this

func tapped() { print("button tapped") }
Button("Tap me").tapAction(tapped)

func tapped(_ button: Button) { print("button tapped") }
Button("Tap me").tapAction(tapped)


It either may be initialized with String or unlimited amount of AttributedStrings

Label("hello ๐Ÿ‘‹ ")
Label().text("hello") // useful if declare label in extension like below
Label(AttributedString("hello").foreground(.red), AttributedString("world").foreground(.green))

set some font from declared identifiers or with system fonts

Label("hello").font(v: .systemFont(ofSize: 15))
Label("hello").font(.sfProBold, 15)

set text color


set text alignment


set amount of lines



TextField("some text")
TextField().text("some text")
TextField.mySuperDuperTextField.text("some text")

set some font from declared identifiers or with system fonts

TextField().font(v: .systemFont(ofSize: 15))
TextField().font(.sfProBold, 15)

set text color


set text alignment



// or use AttributedString to make it colored



remove any text from field easily


set keyboard and content type


set delegate


or get needed events declarative way

TextField().shouldBeginEditing { tf in return true }
           .didBeginEditing { tf in }
           .shouldEndEditing { tf in return true }
           .didEndEditing { tf in }
           .shouldChangeCharacters { tf, range, replacement in return true }
           .shouldClear { tf in return true }
           .shouldReturn { tf in return true }
           .editingDidBegin { tf in }
           .editingChanged { tf in }
           .editingDidEnd { tf in }


SegmentedControl("One", "Two").select(1).changed { print("segment changed to \($0)") }


// iOS10+

Create your own extension for your custom effects to use them easily like in example above

extension UIVisualEffect {
    public static var darkBlur: UIVisualEffect { return UIBlurEffect(style: .dark) }




The same as StackView but with predefined axis and ability to easily add arranged subviews

HStackView (
  Label("hello world").background(.green),
  Label("hello world").background(.red)


The same as StackView but with predefined axis and ability to easily add arranged subviews

VStackView (
  Label("hello world").background(.green),
  Label("hello world").background(.red)


This is really bonus view! :D Almost every app now uses verification codes for login and now you can easily implement that code view with UIKitPlus! :)

                       .digitBorder(.bottom, 1, 0xC6CBD3)
                       .font(.sfProRegular, 32)

func verify(_ code: String) {
  print("entered code: " + code)

Any view






To set radius to all corners


To set custom radius for specific corner

View().corners(10, .topLeft, topRight)
View().corners(10, .topLeft, .bottomRight)
View().corners(10, .topLeft, topRight, .bottomLeft, .bottomRight)

To make you view's corners round automatically by smaller side



To set border on all sides

View().border(1, .black)
View().border(1, 0x000)

To set border on specific side

View().border(.top, 1, .black)
View().border(.left, 1, .black)
View().border(.right, 1, .black)
View().border(.bottom, 1, .black)

To remove border from specific side







View().hidden() // true by default


To rasterize layer, e.g. for better shadow performance

View().rasterize() // true by default


View().shadow() // by default it's black, opacity 1, zero offset, radius 10
View().shadow(.gray, opacity: 0.8, offset: .zero, radius: 5)
View().shadow(0x000000, opacity: 0.8, offset: .zero, radius: 5)


You can shake any view just by calling


And you could customize shake effect

View().shake(values: [-20, 20, -20, 20, -10, 10, -5, 5, 0],
             duration: 0.6,
             axis: .horizontal,
             timing: .easeInEaseOut)
View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
             duration: 0.6,
             axis: .horizontal,
             timing: .easeInEaseOut)

or even create an extension

import UIKitPlus

extension DeclarativeProtocol {
  func myShake() {
      View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
                   duration: 0.6,
                   axis: .horizontal,
                   timing: .easeInEaseOut)


You could create attributed strings in declarative way easily

                         .font(.sfProBold, 15)
                   // or .shadow(offset: .zero, blur: 1, color: .lightGray)



You can set view size by passing width and height values like this

View().size(100, 200)

or square size by passing single value


or view's size can be equal to other view size so when you change size of one view other view will change its size as well

let view1 = View().size(100, 200)
let view2 = View().equalSize(to: view1)

Of course you can specify just width or just height or both but by separate methods


Your view can stick to its superview by any side or even by all sides

// this way it will stick with 0 constant value
// this way it will stick with 10 constant value for all sides
// also you could specify some values manually, but all the rest values are 0 by default
View().edgesToSuperview(top: 16, leading: 16, trailing: -16)
View().edgesToSuperview(trailing: -16, bottom: -16)

you could stick your view to only one side of superview like this

// empty argument means 0 constant value

Any side of your view could also stick to any side of other view

// Sides to superview
View().top(to: .bottom, of: someView, 16) // stick view's top to someView`s bottom with 16pt (by default 0pt)
View().leading(to: .trailing, of: someView)
View().trailing(to: .leading, of: someView)
View().bottom(to: .top, of: someView)
// Center to superview
View().centerX(to: .centerX, of: someView)
View().centerY(to: .centerY, of: someView)
// Dimension Superview
View().width(to: .width, of: someView)
View().height(to: .height, of: someView)

or this way

// Sides to superview
View().edge(.top, toSuperview: someView, .bottom)
View().edge(.leading, toSuperview: someView, .trailing)
View().edge(.trailing, toSuperview: someView, .leading)
View().edge(.bottom, toSuperview: someView, .top)
// Center to superview
View().edge(.centerX, toSuperview: someView, .centerX)
View().edge(.centerY, toSuperview: someView, .centerY)
// Dimension Superview
View().edge(.width, toSuperview: someView, .width)
View().edge(.height, toSuperview: someView, .height)

To build constraints between two relative views

// Sides to another views
View().spacing(.leading, to: relativeView, toSide: .trailing, 16) // last parameter is optional, 0 by default
View().spacing(.trailing, to: relativeView, toSide: .leading)
View().spacing(.top, to: relativeView, toSide: .bottom)
View().spacing(.bottom, to: relativeView, toSide: .top)
// Center to another relative views
View().center(.x, to: relativeView, toSide: .x)
View().center(.y, to: relativeView, toSide: .y)
// Dimension Relative
View().dimension(.width, to: relativeView, toSide: .width)
View().dimension(.height, to: relativeView, toSide: .height)

Your view could be in center of its superview

View().centerInSuperview() // exact center
View().centerInSuperview(10) // exact center +10 by x-axis, and +10 by y-axis
View().centerInSuperview(x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis

also it may be in center of another view

View().center(to: anotherView) // exact center
View().center(to: anotherView, 10) // exact center +10 by x-axis, and +10 by y-axis
View().center(to: anotherView, x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis
Constraints direct access

Ok, let's imagine that you have a view which is sticked to its superview

let view = View().edgesToSuperview()

now your view have top, leading, trailing and bottom constraints to its superview and e.g. you want to change top constraint so you could do it like this

view.top = 16


view.declarativeConstraints.top?.constant = 16

the same way works with all view's constraints, so you can change them or even delete them just by setting them nil.

Another situation if you have a view which have a constrain to another relative view

let centerView = View().background(.black).size(100).centerInSuperview()
let secondView = View().background(.green).size(100).centerXInSuperview().top(to: .bottom, of: centerView, 16)

and for example you want to reach bottom constraint of centerView related to secondView, do it like this

// short way
centerView.outer[.bottom, secondView] = 32 // changes their vertical spacing from 16 to 32
// long way
centerView.declarativeConstraints.outer[.bottom, secondView]?.constant = 32 // changes their vertical spacing from 16 to 32
Layout Margin
// to all sides
// optional sides
View().layoutMargin(top: 10)
View().layoutMargin(left: 10, bottom: 5)
View().layoutMargin(top: 10, right: 5)
// vertical and horizontal
View().layoutMargin(x: 10, y: 5) // top: 5, left: 10, right: 10, bottom: 5
View().layoutMargin(x: 10) // left: 10, right: 10
View().layoutMargin(y: 5) // top: 5, bottom: 5

You could get safeArea safely at any view without #available(iOS 11.0, *) check like this

Constraint values

Any constraint value may be set as CGFloat or with Relation and even Multiplier

// just equal to 10
View().leading(to: .trailing, of: anotherView, 10)

// greaterThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, >=10)

// lessThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, <=10)

// equal to 10 with 1.5 multiplier
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5)

// equal to 10 with 1.5 multiplier and 999 priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! 999)

// equal to 10 with 1.5 multiplier and `.defaultLow` priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! .defaultLow)

// equal to 10 with 999 priority
View().leading(to: .trailing, of: anotherView, 10 ! 999)


With UIKitPlus you're able to use hex colors just by typing them with 0x prefix like 0x000 for black or 0xfff for white.



Add your custom fonts to the project and then declare them like this

import UIKitPlus

extension FontIdentifier {
    public static var sfProBold = FontIdentifier("SFProDisplay-Bold")
    public static var sfProRegular = FontIdentifier("SFProDisplay-Regular")
    public static var sfProMedium = FontIdentifier("SFProDisplay-Medium")

and then use them just like

Button().font(.sfProMedium, 15)


Declare custom colors like this

import UIKit
import UIKitPlus

extension UIColor {
    static var mainBlack: UIColor { return .black  }
    static var otherGreen: UIColor { return 0x3D7227.color  } // 61 114 39

and then use them just like

Label("Hello world").color(.otherGreen).background(.mainBlack)


Declare custom buttons like this

import UIKitPlus

extension Button {
    static var bigBottomWhite: Button {
        return Button().color(.darkGray).color(.black, .highlighted).font(.sfProMedium, 15).background(.white).backgroundHighlighted(.lightGray).circle()
    static var bigBottomGreen: Button {
        return Button().color(.white).font(.sfProMedium, 15).background(.mainGreen).circle()

and then use them like this

Button.bigBottomWhite.size(300, 50).bottomToSuperview(20).centerInSuperview()


Declare custom attributed labels like this

import UIKitPlus

extension Label {
    static var welcomeLogo: Label {
        return .init(AttributedString("My").foreground(.white).font(.sfProBold, 26),
                     AttributedString("App").font(.sfProBold, 26))

and then use them like this

let logo = Label.welcomeLogo.centerInSuperview()


Declare asset images like this

import UIKitPlus

extension Image {
    static var welcomeBackground: Image { return Image("WelcomeBackground") }

and then use them like this

let backgroudImage = Image.welcomeBackground.edgesToSuperview()


Add subviews easily

view.body {


Stars: 64


Used By

Total: 0


Brand new VScrollStack and HScrollStack โค๏ธ - 2019-11-10 03:02:53

Use VScrollStack and HScrollStack as simple as VStack and HStack but with scrolling!

It contains methods from both StackView and ScrollView.

body {
    VScrollStack {
        Text("Text on top")        
        Text("Text 400px from top")
        Text("Text ~800px from top")
        Text("Text at the bottom")
    .spacing(16) // stackview method
    .hideAllIndicators() // scrollview method

New portion of useful things and fixes - 2019-11-07 10:34:58

  • DeclarativeProtocol: implement tag method
  • Image: implement more url methods
  • PickerView: implement textColor methods
  • Button: fix reactive title logic
  • TextField: implement shouldReturnToNextResponder
  • ViewController: improve keyboard notification handlers by retrieving animation curve info to flawlessly show/hide keyboard with $keyboardHeight

Improve root controller to support scenes - 2019-11-07 10:33:12

RootController: implement attach(to scene:)

now in SceneDelegate file you could attach RootController like this

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
        RootViewController().attach(to: scene) // it returns Window and you could store it if you want to

without SceneDelegate you could attach RootController in AppDelegate like this

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var rootViewController: RootViewController { window!.rootViewController as! RootViewController }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        RootViewController().attach(to: window)
        return true

Live preview! ๐ŸŽ‰ - 2019-10-30 18:42:21

Let's imagine that you already have MyViewController in your project

create e.g. Live.swift, paste this code, enable canvas Cmd+Opt+Enter and pin this canvas

#if canImport(SwiftUI) && DEBUG
import SwiftUI
import UIKitPlus
@available(iOS 13.0, *)
struct DemoView_Preview: PreviewProvider {
    static var previews: some SwiftUI.View {
            .previewDevice(PreviewDevice(rawValue: "iPhone X"))

go to MyViewController and enjoy with live preview!

Then just replace MyViewController() to somethings else to switch to preview for another view controller or view.

Finally started working on v1.0 - 2019-09-29 20:14:16

This release still support iOS9+ but it works with Swift 5.1+ only because of function builders and property wrappers ๐Ÿš€ Readme will be updated soon! Stay tuned! P.S. If you can't wait write me a message on discord or take a look at commits.

Localization feature - 2019-08-09 15:46:42

Now you're able to localize any string with any locale.

let myString = String(
    .zh_Hans("ไฝ ๅฅฝ"), 
print(myString) // it will print string relative to your current locale

If somehow current language will no be detected it will use default language which is located in Localization.default and you can change it in runtime to anything you want.

Also you are able to change current language to anything you want by setting Localization.current

New things - 2019-07-22 18:07:42

  • Implement declarative UIPickerView
  • Implement declarative UIDatePicker
  • Implement extension to set different numeric values for different device models
let width: CGFloat = 100 !! .iPhone5(60) !! .iPhone6(70) !! .iPhone6Plus(85)
  • Implement declarative BarButtonItem
  • Implement declarative TableViewCell
  • Implement declarative InputView
  • TextField: add ability to set inputView
  • TextField: add an ability to add multiple callbacks for methods like didBeginEditing, didEndEditing, editingChanged
  • TabViewController: mark as open
  • UIView+Add: add method with [UIView] in addition to variadic
  • Label: add ability to set attributed text after initialization
  • TableView: implement a lot of useful methods
  • TableView: fix initialization issues
  • ScrollView: add convenience initializers
  • ScrollView: add delegate method
  • WrapperView: fix issue when shadow doesn't work with custom rounded corners
  • WrappedViewControlelr: parent view is not generic anymore

Critical fixes and pleasant improvements - 2019-07-20 11:53:16

  • Fix crossconstraints issue when views are added out of order
  • NavigationController allow to set tint color
  • NavigationController allow to hide navigationBar
  • NavigationController allow to enable/disable swipeBack
  • ViewController improve keyboard appearance handlers logic
  • Fix corners + shadow issue
  • View add discardableResult to subviews method
  • View improve tapAction
  • ButtonView add mode method
  • Add subviews method to StackView
  • Improve VerificationCodeView
  • TextFieldView add simple shouldReturn method
  • TextFieldView add ability to add left and right views
  • TextFieldView replace TextField with Self in all returns
  • Improve SegmentedControl
  • Implement ContentInsetAdjustment enum for CollectionView
  • Extend DeclarativeProtocol by adding one more corners method
  • Add centerX to DeclarativeConstraintXSide
  • Extend DeclarativeProtocol with bounds methods
  • Extend NavigationController with animated push and pop methods
  • Implement concat operator for AttributedString
  • Extend UIImage with convenience blurred method
  • Extend UIImage with convenience resize method
  • Implement ImpactFeedback
  • Extend UIDevice with few convenience methods
  • Add ability to print all fonts
  • Fix constraint operators