Swiftpack.co - alobanov11/UISwift as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by alobanov11.
alobanov11/UISwift 2.0.0-alpha.1.7.8
🏰 Declarative UIKit with LivePreview for iOS9+ (best alternative to SwiftUI)
⭐️ 0
🕓 29 weeks ago
iOS
.package(url: "https://github.com/alobanov11/UISwift.git", from: "2.0.0-alpha.1.7.8")

Fork UIKitPlus

MIT License Swift 5.2 Swift 5.2 iPadOS and Catalyst support Cocoapod Swift.Stream

🚀❤️ YOU WILL LOVE UIKIT MORE THAN EVER ❤️🚀


Nothing is impossible!

Build awesome responsive UIs even simpler than with SwiftUI cause you already know everything.


With. Live. Preview. iOS9+.


A LOT OF EXAMPLES

SWIFT.STREAM COMMUNITY IN DISCORD

Requirements

Xcode 13.0+

Swift 5.2+

Good mood

Installation

With CocoaPods

Add the following line to your Podfile:

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

With Swift Package Manager

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

https://github.com/MihaelIsaev/UISwift

IMPORTANT!

To support iOS lower than 13 you have to set -weak_framework SwiftUI in Other Linker Flags in Build Settings.

Without that your app gonna crash on iOS lower than 13 because it will try to load SwiftUI without luck.

Screenshot 2020-03-29 at 03 35 10

Project Template! 🍾

To simplify life with UISwift you can download our template!

For that run the following commands in console

git clone https://github.com/MihaelIsaev/UISwift.git
cp -R UISwift/Templates ~/Library/Developer/Xcode/
rm -rf UISwift

After that you will be able to go to File -> New -> Project and choose UISwift app! 🚀

UISwift App Template Screenshot

💡After project creation you have to install UISwift manually either with Swift Package Manager or with CocoaPods

File Template

Together with project template you will get the file template 👍

Features

1. Delayed constraints

Declare all the constraints in advance before adding view to superview. Even by tags.

Button("Click me").width(300).centerInSuperview()

2. Declarativity

Build everything declarative way. Any view. Any control. Even layers, gestures, colors, fonts, etc.

Text("Hello world").color(.red).alignment(.center).font(.sfProMedium, 15)

3. Reactivity

Use @State for any property, react on any thing, map states to different types, etc.

@State var text = "Hello world"
Text($text)

@State var number = 5
Text($number.map { "\($0)" })

@State var bool = false
Text($bool.map { $0 ? "enabled" : "disabled" })

4. Purity

Everything is pretty clear. Clean short code without magic.

5. SwiftUI-like but still beloved UIKit

Declare subviews like in SwiftUI

body {
    View1()
    View2()
    View3()
    // btw it is NOT limited to 10
}

6. Reusable and extendable

Declare views or its styles in extensions. Subclass views. Use all the power of OOP.

7. All modern features

Diffable data-source (yes yes for iOS9+). Dynamic colors for light/dark mode. Stateable animations. Reactivity.

8. Everything and even more

Built-in ImageLoader, no need in huge 3rd party libs. Just set URL to Image. Fully customizable and overridable.

Image(url: "")
Image(url: "", defaultImage: UIImage(named: "emptyImage")) // set default image to show it while loading
Image(url: "", loader: .defaultRelease) // release image before start loading
Image(url: "", loader: .defaultImmediate) // immediate replace image after loading
Image(url: "", loader: .defaultFade) // replace image with fade effect after loading
Image(url: "", loader: ImageLoader()) // subclass from `ImageLoader` and set you custom loader here

Easy device model and type detection and ability to set values based on that.

Button("Click me").width(400 !! iPhone6(300) !! .iPhone5(200))

Localizable strings

Localization.default = .en // set any localization as default to use it with not covered languages
Localization.current = .en // override current locale
String(.en("Hello"), .fr("Bonjour"), .ru("Привет"))

Custom trait collections.

9. Live Preview

Live preview provided by SwiftUI (available only since macOS Catalina).

The only problem we have is that since names of views are the same in UISwift and SwiftUI we should use aliases like UButton for Button or UView for View, so everything with U prefix. It is only necessary if you want to use live previews, otherwise there is no need to import SwiftUI, so no name conflicts.

Preview single item

💡 You can create as many preview structs as you need

ViewController example

#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)
struct MyViewController_Preview: PreviewProvider, DeclarativePreview {
    static var preview: Preview {
        Preview {
            MainViewController()
        }
        .colorScheme(.dark)
        .device(.iPhoneX)
        .language(.fr)
        .rtl(true)
    }
}
#endif

View example

#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)
struct MyButton_Preview: PreviewProvider, DeclarativePreview {
    static var preview: Preview {
        Preview {
            UButton(String(.en("Hello"), .fr("Bonjour"), .ru("Привет")))
                .circle()
                .background(.blackHole / .white)
                .color(.white / .black)
                .height(54)
                .edgesToSuperview(h: 8)
                .centerYInSuperview()
        }
        .colorScheme(.dark)
        .layout(.fixed(width: 300, height: 64))
        .language(.fr)
        .rtl(true)
    }
}
#endif

Preview group 🔥

It is just convenient way to create multiple previews inside one struct

Limitations:

  • only 10 previews inside group
  • rtl and language properties can be set only to group, not to previews directly
#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)
struct MyPreviewGroup_Preview: PreviewProvider, DeclarativePreviewGroup {
    static var previewGroup: PreviewGroup {
        PreviewGroup { // 1 to 10 previews inside
            Preview {
                MainViewController()
            }
            .colorScheme(.dark)
            .device(.iPhoneX)
            Preview {
                MainViewController()
            }
            .colorScheme(.light)
            .device(.iPhoneX)
            Preview {
                // in this group title will be shown in `fr` language
                UButton(String(.en("Hello"), .fr("Bonjour"), .ru("Привет")))
                    .circle()
                    .background(.blackHole / .white)
                    .color(.white / .black)
                    .height(54)
                    .edgesToSuperview(h: 8)
                    .centerYInSuperview()
            }
            .colorScheme(.dark)
            .layout(.fixed(width: 300, height: 64))
        }
        .language(.fr) // limited to group
        .rtl(true) // limited to group
    }
}
#endif

Usage

import UISwift

Even no need to import UIKit at all!

Constraints

Solo

aspectRatio
/// 1:1
View().aspectRatio()

/// 1:1 low priority
View().aspectRatio(priority: .defaultLow)

/// 4:3
View().aspectRatio(4 / 3)

/// 4:3 low priority
View().aspectRatio(priority: .defaultLow)
width
/// 100pt
View().width(100)

/// Stateable width
@State var width: CGFloat = 100

View().width($width)

/// Stateable but based on different type
@State var expanded = false

View().width($expanded.map { $0 ? 200 : 100 })

/// Different value for different devices
/// 80pt for iPhone5, 120pt for any iPad, 100pt for any other devices
View().width(100 !! .iPhone5(80) !! .iPad(150))
height
/// 100pt
View().height(100)

/// Stateable width
@State var height: CGFloat = 100

View().height($width)

/// Stateable but based on different type
@State var expanded = false

View().height($expanded.map { $0 ? 200 : 100 })

/// Different value for different devices
/// 80pt for iPhone5, 120pt for any iPad, 100pt for any other devices
View().height(100 !! .iPhone5(80) !! .iPad(150))
size
/// width 100pt, height 100pt
View().size(100)

/// width 100pt, height 200pt
View().size(100, 200)

/// Stateable
@State var width: CGFloat = 100
@State var height: CGFloat = 100

View().size($width, 200)
View().size(100, $height)
View().size($width, $height)

/// for both
@State var size: CGFloat = 100
View().size($size)

/// Stateable but based on different type
@State var expanded = false

View().size($expanded.map { $0 ? 200 : 100 })
View().size(100, $expanded.map { $0 ? 200 : 100 })
View().size(100 !! .iPad(200), $expanded.map { $0 ? 200 !! .iPad(300) : 100 !! .iPad(200) })
View().size($width, $expanded.map { $0 ? 200 : 100 })
View().size($expanded.map { $0 ? 200 : 100 }, 100)
View().size($expanded.map { $0 ? 200 : 100 }, $height)

Read and write view's solo constraints directly. And even animate them.

let v = View()
v.width = 100
v.height = 100
UIViewPropertyAnimator(duration: 0.5, curve: .easeInOut) {
    v.width = 200
    v.height = 300
}.startAnimation()

Super

edges
/// all edges to superview 0pt
View().edgesToSuperview()

/// all edges to superview 16pt
View().edgesToSuperview(16)

/// horizontal edges: 16pt, vertical edges: 24pt
View().edgesToSuperview(16, 24)

/// horizontal edges: 16pt
View().edgesToSuperview(h: 16)

/// vertical edges: 24pt
View().edgesToSuperview(v: 24)

/// each edge to different value to superview
View().edgesToSuperview(top: 24, leading: 16, trailing: -16, bottom: -8)
top
/// 16pt to top of superview
View().topToSuperview(16)

/// 16pt to safeArea top of superview
View().topToSuperview(16, safeArea: true)

/// Stateable
@State var top: CGFloat = 16

View().topToSuperview($top)

/// Stateable but based on different type
@State var expanded = false

View().topToSuperview($expanded.map { $0 ? 0 : 16 })
leading
/// 16pt to leading of superview
View().leadingToSuperview(16)

/// all the same as with topToSuperview
trailing
/// -16pt to trailing of superview
View().trailingToSuperview(-16)

/// all the same as with topToSuperview
bottom
/// -16pt to bottom of superview
View().leadingToSuperview(-16)

/// all the same as with topToSuperview
centerX
/// right in center of superview horizontally
View().centerXInSuperview()

/// 16pt from horizontal center of superview
View().centerXToSuperview(16)

/// all the same as with topToSuperview
centerY
/// right in center of superview vertically
View().centerYInSuperview()

/// 16pt from vertical center of superview
View().centerYToSuperview(16)

/// all the same as with topToSuperview
center
/// right in center of superview both horizontally and vertically
View().centerInSuperview()

/// 16pt from horizontal center of superview, 8pt from vertical center of superview
View().centerInSuperview(x: 16, y: 8)

/// all the same as with topToSuperview
width
/// equal width with superview
View().widthToSuperview()

/// equal width with superview with low priority
View().widthToSuperview(priority: .defaultLow)

/// half width of superview
View().widthToSuperview(multipliedBy: 0.5)

/// half width of superview with low priority
View().widthToSuperview(multipliedBy: 0.5, priority: .defaultLow)

/// all the same as with topToSuperview
height
/// equal height with superview
View().heightToSuperview()

/// all the same as with widthToSuperview

Read and write view's super constraints directly. And even animate them.

let v = View()
v.top = 24
v.leading = 16
v.trailing = 16
v.bottom = -24
UIViewPropertyAnimator(duration: 0.5, curve: .easeInOut) {
    v.top = 0
    v.leading = 8
    v.trailing = 8
    v.bottom = 0
}.startAnimation()

Relative

top
View().top(to: otherView)
View().top(to: otherView, 16)
View().top(to: otherView, $topStateValue)
View().top(to: .top, of: otherView)
View().top(to: .top, of: otherView, $topStateValue)
leading
View().leading(to: otherView)

/// all the same as for top(to:)
trailing
View().trailing(to: otherView)

/// all the same as for top(to:)
bottom
View().bottom(to: otherView)

/// all the same as for top(to:)
left
View().left(to: otherView)

/// all the same as for top(to:)
right
View().right(to: otherView)

/// all the same as for top(to:)
centerX
View().centerX(to: otherView)

/// all the same as for top(to:)
centerY
View().centerY(to: otherView)

/// all the same as for top(to:)
center
View().center(to: otherView)

/// all the same as for top(to:)
width
View().width(to: otherView)

/// all the same as for top(to:)
height
View().height(to: otherView)

/// all the same as for top(to:)
equal
/// just a convenient method to width&height
View().equalSize(to: otherView)

/// all the same as for top(to:)

💡 TIP: Feel free to use State, ExpressableState, and values based on device type everywhere

Relative constraints by tags 🔥

Screenshot 2020-04-18 at 05 47 57

Really often we have to create some views with constraints related to each other 😃

The classic way is to create a variable with view somewhere outside, like this

let someView = UView()

then we used it with other views to make relative constraints

UView {
    someView.size(200).background(.red).centerInSuperview()
    UView().size(100).background(.cyan).centerXInSuperview().top(to: someView)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: someView)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: someView)
    UView().size(100).background(.green).centerYInSuperview().left(to: someView)
}

But if it's not necessary to declare view outside the you can use tag! And easily rely to it from other views!

UView {
    UView().size(200).background(.red).centerInSuperview().tag(7)
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
}

Even order doesn't matter 🤗

UView {
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
    UView().size(200).background(.red).centerInSuperview().tag(7)
}

You even can add view later and all related views will immediately stick to it once it's added 🚀

let v = UView {
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    UIView.animate(withDuration: 1) {
        v.body {
            UView().size(200).background(.red).centerInSuperview().tag(7)
        }
    }
}

Extra

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)

More about 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

or

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
Root View Controller 🍀

Detailed instruction

View

alias is UView

View may be created with empty initializer

View()

or you can put subviews into it right while initialization

View {
    View()
    View()
}

or you can wrap some view using inline keyword so that inner view will stick all edges to superview

View(inline: MKMapView())

also you can add subviews to that superview by calling .body { ... } method. even multiple times.

View().body {
    View()
    View()
}.body {
    View()
}.body {
    View()
    View()
    View()
}
VerificationCodeView

// implemented. to be described more

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 UISwift! :)

VerificationCodeField().digitWidth(64)
                       .digitsMargin(25)
                       .digitBorder(.bottom, 1, 0xC6CBD3)
                       .digitColor(0x171A1D)
                       .font(.sfProRegular, 32)
                       .entered(verify)

func verify(_ code: String) {
  print("entered code: " + code)
}
VisualEffectView
// implemented. to be described more

VisualEffectView(.darkBlur)
VisualEffectView(.lightBlur)
VisualEffectView(.extraLightBlur)
// iOS10+
VisualEffectView(.prominent)
VisualEffectView(.regular)

// iOS13+ (but can be used since iOS9+)
// automatic dynamic effect for light and dark modes
VisualEffectView(.darkBlur, .lightBlur) // effect will be switched automatically. darkBlur is for light mode.

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) }
}
WrapperView

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

WrapperView {
  View().background(.red).shadow()
}.background(.green).shadow()

and you could specify innerView`s padding right here

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

// implemented. to be described

Impact Feedback

My favourite feature.

ImpactFeedback.error()
ImpactFeedback.success()
ImpactFeedback.selected()
ImpactFeedback.bzz()
Localization 🇮🇸🇩🇪🇯🇵🇲🇽
// set any localization as default
Localization.default = .en

// override current locale
Localization.current = .en

// create string relative to current language
let myString = String(
    .en("Hello"),
    .fr("Bonjour"),
    .ru("Привет"),
    .es("Hola"),
    .zh_Hans("你好"),
    .ja("こんにちは"))
print(myString)

By default current language is equal to Locale.current but you can change it by setting Localizer.current = .en. Also localizer have default language in case if user's language doesn't match any in your string, and you could set it just by calling Localizer.default = .en.

Also you can use localizable strings directly in Button, Text, TextView, TextField and AttributedString

Text(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))

TextView(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))
    .placeholder(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))

TextField(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))
    .placeholder(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))

Button(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))
Button().title(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"), state: .highlighted)

AttrStr(.en("Hello"), .ru("Привет"), .fr("Bonjour"), .es("Hola"))

But how to use this awesome localization with 10+ languages in the app?

Just create a dedicated localization file (e.g. Localization.swift) like this

extension String {
    static func transferTo(_ wallet: String) -> String {
        String(.en("Transfer to #\(wallet)"),
                  .ru("Перевод на #\(wallet)"),
                  .zh("转移到 #\(wallet)"),
                  .ja("#\(wallet)に転送"),
                  .es("Transferir a #\(wallet)"),
                  .fr("Transférer au #\(wallet)"),
                  .sv("Överför till #\(wallet)"),
                  .de("Übertragen Sie auf #\(wallet)"),
                  .tr("\(wallet) numarasına aktar"),
                  .it("Trasferimento al n. \(wallet)"),
                  .cs("Převod na #\(wallet)"),
                  .he("\(wallet) העבר למספר"),
                  .ar("\(wallet)#نقل إلى"))
    }
    static var copyLink: String {
        String(.en("Copy link to clipboard"),
                  .ru("Скопировать ссылку"),
                  .zh("复制链接到剪贴板"),
                  .ja("リンクをクリップボードにコピー"),
                  .es("Copiar enlace al portapapeles"),
                  .fr("Copier le lien dans le presse-papiers"),
                  .sv("Kopiera länk till urklipp"),
                  .de("Link in Zwischenablage kopieren"),
                  .tr("Bağlantıyı panoya kopyala"),
                  .it("Copia il link negli appunti"),
                  .cs("Zkopírujte odkaz do schránky"),
                  .he("העתק קישור ללוח"),
                  .ar("نسخ الرابط إلى الحافظة"))
    }
    static var copyLinkSucceeded: String {
        String(.en("Link has been copied to clipboard"),
                  .ru("Ссылка успешно скопирована в буфер обмена"),
                  .zh("链接已复制到剪贴板"),
                  .ja("リンクがクリップボードにコピーされました"),
                  .es("El enlace ha sido copiado al portapapeles"),
                  .fr("Le lien a été copié dans le presse-papiers"),
                  .sv("Länken har kopierats till Urklipp"),
                  .de("Der Link wurde in die Zwischenablage kopiert"),
                  .tr("Bağlantı panoya kopyalandı"),
                  .it("Il link è stato copiato negli appunti"),
                  .cs("Odkaz byl zkopírován do schránky"),
                  .he("הקישור הועתק ללוח"),
                  .ar("تم نسخ الرابط إلى الحافظة"))
    }
    static var shareNumber: String {
        String(.en("Share number"),
                  .ru("Поделиться номером"),
                  .zh("分享号码"),
                  .ja("共有番号"),
                  .es("Compartir número"),
                  .fr("Numéro de partage"),
                  .sv("Aktienummer"),
                  .de("Teilenummer"),
                  .tr("Numarayı paylaş"),
                  .it("Condividi il numero"),
                  .cs("Sdílejte číslo"),
                  .he("מספר שתף"),
                  .ar("رقم السهم"))
    }
    static var shareLink: String {
        String(.en("Share link"),
                  .ru("Поделиться ссылкой"),
                  .zh("分享链接"),
                  .ja("共有リンク"),
                  .es("Compartir enlace"),
                  .fr("Lien de partage"),
                  .sv("Dela länk"),
                  .de("Einen Link teilen"),
                  .tr("Linki paylaş"),
                  .it("Condividi il link"),
                  .cs("Sdílet odkaz"),
                  .he("שתף קישור"),
                  .ar("مشاركة الرابط"))
    }
}

And then use localized string all over the app this easy way

Text(.transferTo("123")) // Transfer to #123
Text(.copyLinkSucceeded) // Copy link to clipboard
Button(.shareNumber) // Share number
Button(.shareLink) // Share link
View Controller

// implemented. to be described

Status bar style

In any ViewController you can set statusBarStyle and all its values are iOS9+.

override var statusBarStyle: StatusBarStyle { .default }
override var statusBarStyle: StatusBarStyle { .dark }
override var statusBarStyle: StatusBarStyle { .light }
Colors
/// Simple color
UIColor.red

/// Automatic dynamic color: black for light mode, white for dark mode
UIColor.black / UIColor.white

/// color in hex, represented as int and supported by all color properties
0xFF0000

/// hex color converted to UIColor
0xFF0000.color

/// hex colors as dynamic UIColor
0x000.color / 0xfff.color

/// color with alpha
UIColor.white.alpha(0.5)

/// hex color with alpha
0xFFFFFF.color.alpha(0.5)

Declare custom colors like this

import UIKit
import UISwift

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)
Fonts
// implemented. to be described

/// helper to print all the fonts in console (debug only)
UIFont.printAll()

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

import UISwift

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)
Gestures

Detailed instruction

States

alias is UState

/// usual
@State var myState = UIColor.red
@State var myState = ""
@State var myState = 0
// etc.

/// expressable
$boolStateToColor.map { $0 == true ? .red : .green }
$boolStateToString.map { !$0 ? "night" : "day" }

/// mix to Int states into one String expressable
$state1.and($state2).map { $0.left > $0.right ? "higher" : "lower" }
Attributed Strings
AttributedString("hello").background(.gray)
                         .foreground(.red)
                         .font(.sfProBold, 15)
                         .paragraphStyle(.default)
                         .ligature(1)
                         .kern(1)
                         .strikethroughStyle(1)
                         .underlineStyle(.patternDash)
                         .strokeColor(.purple)
                         .strokeWidth(1)
                         .shadow()
                   // or .shadow(offset: .zero, blur: 1, color: .lightGray)
                         .textEffect("someEffect")
                         .attachment(someAttachment)
                         .link("http://github.com")
                         .baselineOffset(1)
                         .underlineColor(.cyan)
                         .strikethroughColor(.magenta)
                         .obliqueness(1)
                         .expansion(1)
                         .glyphForm(.horizontal)
                         .writingDirection(.rightToLeft)

/// also use shorter alias
AttrStr("hello").foreground(.red)
// or even just
"hello".foreground(.red)
Animations

// implemented. to be described

Activity Indicator

// implemented. to be described

Bar Button Item

// implemented. to be described

Button

alias is UButton

// to be described more

Button()
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").onTapGesture { print("button tapped") }
Button("Tap me").onTapGesture { button in
    print("button tapped")
}

or like this

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

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

Declare custom buttons like this

import UISwift

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()
Collection
// implemented. to be described

// difference between Collection and CollectionView
// flow layouts
ControlView

// implemented. to be described

DatePicker

// implemented. to be described

DynamicPickerView

// implemented. to be described

StackView

alias is UStackView

// implemented. to be described

StackView().axis(.vertical)
           .alignment(.fill)
           .distribution(.fillEqually)
           .spacing(16)
VStack

alias is UVStack

// implemented. to be described more The same as StackView but with predefined axis and ability to easily add arranged subviews

VStack (
  Text("hello world").background(.green),
  VSpace(16) // 16pt delimiter
  Text("hello world").background(.red)
)
.spacing(10)
.alignment(.left)
.distribution(...)
VScrollStack
// implemented. to be described

/// it is the same as VStack but it is combined with ScrollView
HStack

alias is UHStack

// implemented. to be described more The same as StackView but with predefined axis and ability to easily add arranged subviews

HStack (
  Text("hello world").background(.green),
  HSpace(16) // 16pt delimiter
  Text("hello world").background(.red)
)
.spacing(10)
.alignment(.left)
.distribution(...)
HScrollStack
// implemented. to be described

/// it is the same as HStack but it is combined with ScrollView
HSpace
/// just a horizontal delimiter
HSpace(16)
/// alternatively
View().width(16)
VSpace
/// just a vertical delimiter
VSpace(16)
/// alternatively
View().height(16)
Space
/// just a flexible space for stack views
Space()
/// alternatively
View()
HUD

// implemented. to be described

Image

alias is UImage

// to be described more

Declare asset images like this

import UISwift

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

and then use them like this

let backgroudImage = Image.welcomeBackground.edgesToSuperview()

With built-in ImageLoader

Image(url: "")
Image(url: "", defaultImage: UIImage(named: "emptyImage")) // set default image to show it while loading
Image(url: "", loader: .defaultRelease) // release image before start loading
Image(url: "", loader: .defaultImmediate) // immediate replace image after loading
Image(url: "", loader: .defaultFade) // replace image with fade effect after loading
Image(url: "", loader: ImageLoader()) // subclass from `ImageLoader` and set you custom loader here
InputView

// implemented. to be described

List

alias is UList

// implemented. to be described

also describe auto-DIFF with Identable models
TableView

// implemented. to be described

PickerView

// implemented. to be described

RefreshControl

// implemented. to be described

ScrollView

// implemented. to be described more

ScrollView().paging(true).scrolling(false).hideIndicator(.horizontal)
ScrollView().paging(true).scrolling(false).hideAllIndicators()
ScrollView().contentInset(.zero)
ScrollView().contentInset(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().contentInset(top: 10, bottom: 10)
ScrollView().scrollIndicatorInsets(.zero)
ScrollView().scrollIndicatorInsets(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().scrollIndicatorInsets(top: 10, bottom: 10)
SegmentedControl

alias is USegmentedControl

// implemented. to be described more

@State var selectedItem = 0
SegmentedControl("One", "Two").select($selectedItem)
// or simply
SegmentedControl("One", "Two").select(0).changed { print("segment changed to \($0)") }
SliderView

// implemented. to be described

Stepper

alias is UStepper

// implemented. to be described

TextField

alias is UTextField

// implemented. to be described

// format with AnyFormat
TextField()
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

TextField().color(.red)

set text alignment

TextField().alignment(.center)

placeholder

TextField().placeholder("email")
// or use AttributedString to make it colored
TextField().placeholder(AttributedString("email").foreground(.green))

secure

TextField().secure()

remove any text from field easily

TextField().cleanup()

set keyboard and content type

TextField().keyboard(.emailAddress).content(.emailAddress)

set delegate

TextField().delegate(self)

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 }
Text (aka UILabel)

alias is UText or just Label

// to be described more 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.mySuperLabel.text("hello")
Label("hello".foreground(.red), "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

Label("hello").color(.red)

set text alignment

Label("hello").alignment(.center)

set amount of lines

Label("hello").lines(1)
Label("hello\nworld").lines(0)
Label("hello\nworld").lines(2)
Label("hello\nworld").multiline()

Declare custom attributed labels like this

import UISwift

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()
TextView

alias is UTextView

// implemented. to be described

Toggle

alias is UToggle

// implemented. to be described

Properties

All the properties are available to be set declaratively and can be binded to State or ExpressableState.

A lot of layer properties are available directly and have convenient initializers.

Alpha
View().alpha(0)
View().alpha($alphaState)
View().alpha($boolState.map { $0 ? 1 : 0 })
Background
View().background(.red)
View().background(0xff0000)
View().background($colorState)
View().background($boolState.map { $0 ? .red : .green })
Borders

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

.removeBorder(.top)
Bounds
// implemented. to be described
Compression Resistance
// implemented. to be described
Corners

To set radius to all corners

View().corners(10)
View().corners($cornerRadiusState)

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 your view's corners round automatically by smaller side

View().circle()
Hidden
View().hidden() // will set `true` by default
View().hidden(true)
View().hidden(false)
View().hidden($hiddenState)
View().hidden($stringState.map { $0.count > 0 })
Hugging Priority
// implemented. to be described
Itself
// implemented. to be described
Layout Margin
// to all sides
View().layoutMargin(10)
// 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
Focus to next responder or resign
// implemented. to be described
Opacity
View().opacity(0)
View().opacity($alphaState)
View().opacity($boolState.map { $0 ? 1 : 0 })
Rasterize

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

View().rasterize() // true by default
View().rasterize(true)
View().rasterize(false)
Shadow
// to be described more

// and with mroe than one shadow
// and with state, expressableState
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)
Shake

You can shake any view just by calling

View().shake()

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 UISwift

extension DeclarativeProtocol {
  func myShake() {
      View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
                   duration: 0.6,
                   axis: .horizontal,
                   timing: .easeInEaseOut)
  }
}
Tag
View().tag(0)
Tint
View().tint(.red)
View().tint(0xff0000)
View().tint($colorState)
View().tint($boolState.map { $0 ? .red : .green })
User Interaction
// implemented. to be described

Examples

Example app is here

Example 1

import UISwift

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

    override func buildUI() {
        super.buildUI()
        body {
            view1.background(.black).size(100).centerInSuperview()
            View().background(.red).size(30, 20).centerXInSuperview().top(to: .bottom, of: view1, 16)
        }
    }
}

Example 2

import UISwift

// 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() {
        super.buildUI()
        view.backgroundColor = .black
        body {
            Button.back.onTapGesture { print("back tapped") }
            Label.welcome.text("Welcome").centerXInSuperview().topToSuperview(62, safeArea: true)
            VStack {
                TextField.welcome.text($email).placeholder("Email").keyboard(.emailAddress).content(.emailAddress)
                TextField.welcome.text($password).placeholder("Password").content(.password).secure()
                View().height(10) // just to add extra space
                Button.bigBottomGreen.title("Sign In").onTapGesture(signIn)
            }.edgesToSuperview(top: 120, leading: 16, trailing: -16)
        }
    }

    func signIn() {
        // do an API call to your server with awesome CodyFire lib 😉
    }
}

And you just need a few extensions to make it work

// PRO-TIP:
// 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 {
        TextField()
            .height(40)
            .background(.clear)
            .color(.black)
            .tint(.mainGreen)
            .border(.bottom, 1, .gray)
            .font(.sfProRegular, 16)
    }
}
extension Button {
    static var back: Button { return Button("backIcon").topToSuperview(64).leadingToSuperview(24) }
    static var bigBottomGreen: Button {
        Button()
            .color(.white)
            .font(.sfProMedium, 15)
            .background(.green)
            .height(50)
            .circle()
            .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.

GitHub

link
Stars: 0
Last commit: 3 days ago
jonrohan Something's broken? Yell at me @ptrpavlik. Praise and feedback (and money) is also welcome.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics