MIT License Swift 5.5 Swift 5.5 iPadOS and Catalyst support macOS support Cocoapod Swift.Stream

🚀❤️ 你会比以往任何时候都更喜欢 UIKIT ❤️🚀


一切皆有可能!

构建出色的响应式 UI 比 SwiftUI 还要简单,因为你已经知道一切


支持实时预览。iOS9+。



示例

我们的 DISCORD 社区

要求

Xcode 13.0+

Swift 5.5+

好心情

安装

使用 CocoaPods

将以下行添加到你的 Podfile

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

使用 Swift Package Manager

在 Xcode 13.0+ 中,前往 File -> Swift Packages -> Add Package Dependency 并在其中输入此仓库的 URL

https://github.com/MihaelIsaev/UIKitPlus

重要!

自版本 2 以来,有很多优点和修复,并且你的项目看起来会更干净,因为不再有 AppDelegate 和 SceneDelegate,一切都在幕后,就像 SwiftUI 一样,但使用任何 AppDelegate/SceneDelegate 方法都非常明显且方便。

通过使用新的项目模板创建一个项目来查看它!

重要!

要支持低于 13 的 iOS 版本,你必须在 Build SettingsOther Linker Flags 中设置 -weak_framework SwiftUI

如果没有它,你的应用程序将在低于 13 的 iOS 版本上崩溃,因为它会尝试加载 SwiftUI 而无法成功。

Screenshot 2020-03-29 at 03 35 10

项目模板! 🍾

为了简化 UIKitPlus 的使用,你可以下载我们的模板!

为此,请在控制台中运行以下命令

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

之后,你将能够转到 File -> New -> Project 并选择 UIKitPlus 应用程序! 🚀

UIKitPlus App Template Screenshot

💡创建项目后,你必须手动安装 UIKitPlus,无论是通过 Swift Package Manager 还是通过 CocoaPods

文件模板

连同项目模板,你将获得文件模板 👍

特性

1. 延迟约束

在将视图添加到父视图之前提前声明所有约束。甚至可以通过标签。

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

2. 声明式

以声明式方式构建一切。任何视图。任何控件。甚至图层、手势、颜色、字体等。

UText("Hello world").color(.red).alignment(.center).font(.sfProMedium, 15)
// or
UText("Hello ".color(.red).font(.sfProMedium, 15), "world".color(.green).font(.sfProBold, 15)).alignment(.center)
// or even
"Hello world".color(.red).alignment(.center).font(.sfProMedium, 15)

3. 响应式

对任何属性使用 @UState,对任何事物做出反应,将状态映射到不同的类型等。

@UState var text = "Hello world"
UText($text)

@UState var number = 5
UText($number.map { "\($0)" })

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

4. 纯粹性

一切都很清楚。干净简洁的代码,没有魔法。

5. 类似 SwiftUI,但仍然是挚爱的 UIKit

像在 SwiftUI 中一样声明子视图(但不要忘记我们仍然在 UIKit 中并使用自动布局)

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

6. 可重用和可扩展

在扩展中声明视图或其样式。子类化视图。使用 OOP 的所有力量。

7. 所有现代功能

Diffable data-source(是的,iOS9+ 支持)。浅色/深色模式的动态颜色。可状态动画。响应性。

8. 一切甚至更多

内置 ImageLoader,无需庞大的第三方库。只需将 URL 设置为 Image。完全可定制和可覆盖。

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

轻松的设备型号和类型检测,并能够基于此设置值。

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

本地化字符串

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("Привет"))

自定义特征集合。

9. 实时预览

由 SwiftUI 提供的实时预览(仅自 macOS Catalina 起可用)。

我们遇到的唯一问题是,由于 UIKitPlusSwiftUI 中的视图名称相同,我们应该使用别名,例如 UButton 代表 ButtonUView 代表 View,因此所有都带有 U 前缀。只有在你想要使用实时预览时才需要这样做,否则无需导入 SwiftUI,因此没有名称冲突。

预览单个项目

💡 你可以根据需要创建任意数量的预览结构

ViewController 示例

#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 示例

#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

预览组 🔥

这只是一种在单个结构中创建多个预览的便捷方法

限制

#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

用法

import UIKitPlus

甚至根本不需要导入 UIKit

约束

独立

aspectRatio
/// 1:1
UView().aspectRatio()

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

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

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

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

UView().width($width)

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

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

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

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

UView().height($width)

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

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

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

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

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

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

/// for both
@UState var size: CGFloat = 100
UView().size($size)

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

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

直接读取和写入视图的独立约束。甚至可以对它们进行动画处理。

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

父视图

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

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

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

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

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

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

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

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

UView().topToSuperview($top)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/// all the same as with widthToSuperview

直接读取和写入视图的父视图约束。甚至可以对它们进行动画处理。

let v = UView()
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()

相对

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

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

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

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

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

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

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

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

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

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

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

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

💡 提示:随时使用 UState 和基于设备类型的值

通过标签的相对约束 🔥

Screenshot 2020-04-18 at 05 47 57

我们经常需要创建一些约束相互关联的视图 😃

经典方法是在外部某个地方创建一个带有视图的变量,如下所示

let someView = UView()

然后我们将其与其他视图一起使用以进行相对约束

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

但是如果没有必要在外部声明视图,则可以使用标签!并轻松地从其他视图依赖它!

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

甚至顺序也无关紧要 🤗

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

你甚至可以稍后添加视图,所有相关视图一旦添加就会立即粘附到它 🚀

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

额外

任何约束值都可以设置为 CGFloat 或带有 Relation 甚至 Multiplier

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

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

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

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

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

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

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

有关约束直接访问的更多信息

好的,让我们想象一下你有一个粘附到其父视图的视图

let view = UView().edgesToSuperview()

现在你的视图对其父视图具有顶部、前导、后导和底部约束,例如,你想要更改 top 约束,你可以这样做

view.top = 16

或者

view.declarativeConstraints.top?.constant = 16

同样的方式适用于所有视图的约束,因此你可以更改它们,甚至可以通过将它们设置为 nil 来删除它们。

另一种情况,如果你有一个约束到另一个相关视图的视图

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

例如,你想要访问与 secondView 相关的 centerView 的底部约束,请像这样操作

// 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
根视图控制器 🍀

详细说明

View

别名是 UView

View 可以使用空的初始化器创建

UView()

或者你可以在初始化时将子视图放入其中

UView {
    UView()
    UView()
}

或者你可以使用 inline 关键字包装一些视图,以便内部视图粘附到父视图的所有边缘

UView(inline: MKMapView())

你还可以通过调用 .body { ... } 方法将子视图添加到该父视图。甚至多次。

UView().body {
    UView()
    UVSpace(8)
    UView()
}.body {
    UView()
}.body {
    UView()
    UView()
    UView()
}
VerificationCodeView

// 已实现。待更详细描述

这真是一个奖励视图! :D 现在几乎每个应用程序都使用验证码进行登录,现在你可以使用 UIKitPlus 轻松实现该代码视图! :)

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

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

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

为你自己的自定义效果创建你自己的扩展,以便像上面的示例一样轻松使用它们

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

这是一个简单的 View,但能够使用内部视图进行初始化

UWrapperView {
  UView().background(.red).shadow()
}.background(.green).shadow()

你可以在此处指定 innerView 的填充

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

// 已实现。待描述

冲击反馈

我最喜欢的功能。

ImpactFeedback.error()
ImpactFeedback.success()
ImpactFeedback.selected()
ImpactFeedback.bzz()
本地化 🇮🇸🇩🇪🇯🇵🇲🇽
// 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)

默认情况下,当前语言等于 Locale.current,但你可以通过设置 Localizer.current = .en 来更改它。如果用户的语言与你的字符串中的任何语言都不匹配,则本地化器也具有 default 语言,你可以通过调用 Localizer.default = .en 来设置它。

你还可以直接在 Button、Text、TextView、TextField 和 AttributedString 中使用可本地化的字符串

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

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

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

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

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

但是如何在应用程序中使用这种具有 10 多种语言的超棒本地化?

只需创建一个专用的本地化文件(例如 Localization.swift),如下所示

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("مشاركة الرابط"))
    }
}

然后以这种简单的方式在整个应用程序中使用本地化字符串

UText(.transferTo("123")) // Transfer to #123
UText(.copyLinkSucceeded) // Copy link to clipboard
UButton(.shareNumber) // Share number
UButton(.shareLink) // Share link
视图控制器

// 已实现。待描述

状态栏样式

在任何 UViewController 中,你可以设置 statusBarStyle,并且其所有值都是 iOS9+。

override var statusBarStyle: StatusBarStyle { .default }
override var statusBarStyle: StatusBarStyle { .dark }
override var statusBarStyle: StatusBarStyle { .light }
颜色
/// 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)

像这样声明自定义颜色

import UIKitPlus

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

然后像这样使用它们

UText("Hello world").color(.otherGreen).background(.mainBlack)
字体
// implemented. to be described

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

将你的自定义字体添加到项目中,然后像这样声明它们

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

然后像这样使用它们

UButton().font(.sfProMedium, 15)
手势

详细说明

状态

别名是 UState

/// usual
@UState var myState = UIColor.red
@UState var myState = ""
@UState 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 > $1 ? "higher" : "lower" }
属性化字符串
"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)
动画

// 已实现。待描述

活动指示器

// 已实现。待描述

栏按钮项

// 已实现。待描述

按钮

别名是 UButton

// 待更详细描述

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

背景和突出显示状态的背景

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

不同状态的标题颜色

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

从声明的标识符或使用系统字体设置一些字体

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

添加图像

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

你可以轻松处理点击操作

UButton("Tap me").onTapGesture { print("button tapped") }
UButton("Tap me").onTapGesture { button in
    print("button tapped")
}

或者像这样

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

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

像这样声明自定义按钮

import UIKitPlus

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

然后像这样使用它们

UButton.bigBottomWhite.size(300, 50).bottomToSuperview(20).centerInSuperview()
集合
// implemented. to be described

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

// 已实现。待描述

DatePicker

// 已实现。待描述

DynamicPickerView

// 已实现。待描述

StackView

别名是 UStackView

// 已实现。待描述

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

别名是 UVStack

// 已实现。待更详细描述StackView 相同,但具有预定义的轴并能够轻松添加排列的子视图

UVStack (
  UText("hello world").background(.green),
  UVSpace(16) // 16pt delimiter
  UText("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

别名是 UHStack

// 已实现。待更详细描述StackView 相同,但具有预定义的轴并能够轻松添加排列的子视图

UHStack (
  UText("hello world").background(.green),
  UHSpace(16) // 16pt delimiter
  UText("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
UHSpace(16)
/// alternatively
UView().width(16)
VSpace
/// just a vertical delimiter
UVSpace(16)
/// alternatively
UView().height(16)
Space
/// just a flexible space for stack views
USpace()
/// alternatively
UView()
HUD

// 已实现。待描述

Image

别名是 UImage

// 待更详细描述

像这样声明资产图像

import UIKitPlus

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

然后像这样使用它们

let backgroudImage = UImage.welcomeBackground.edgesToSuperview()

使用内置的 ImageLoader

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

// 已实现。待描述

List

别名是 UList

// implemented. to be described

also describe auto-DIFF with Identable models
TableView

// 已实现。待描述

PickerView

// 已实现。待描述

RefreshControl

// 已实现。待描述

ScrollView

// 已实现。待更详细描述

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

别名是 USegmentedControl

// 已实现。待更详细描述

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

// 已实现。待描述

Stepper

别名是 UStepper

// 已实现。待描述

TextField

别名是 UTextField

// implemented. to be described

// format with AnyFormat
UTextField()
UTextField("some text")
UTextField().text("some text")
UTextField.mySuperDuperTextField.text("some text")

从声明的标识符或使用系统字体设置一些字体

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

设置文本颜色

UTextField().color(.red)

设置文本对齐方式

UTextField().alignment(.center)

占位符

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

安全

UTextField().secure()

轻松从字段中删除任何文本

UTextField().cleanup()

设置键盘和内容类型

UTextField().keyboard(.emailAddress).content(.emailAddress)

监听用户是否正在输入

UTextField().typing($isTyping, interval: 2) // very useful for chats

设置代理

UTextField().delegate(self)

或以声明方式获取所需的事件

UTextField().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)

别名是 UText 或仅 Label

// 待更详细描述 它可以是使用 String 或无限数量的 AttributedString 初始化的

UText("hello 👋 ")
UText().text("hello") // useful if declare label in extension like below
UText.mySuperLabel.text("hello")
UText("hello".foreground(.red), "world".foreground(.green))

从声明的标识符或使用系统字体设置一些字体

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

设置文本颜色

UText("hello").color(.red)

设置文本对齐方式

UText("hello").alignment(.center)

设置行数

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

像这样声明自定义属性化标签

import UIKitPlus

extension UText {
    static var welcomeLogo: UText {
        UText("My".foreground(.white).font(.sfProBold, 26), "App".font(.sfProBold, 26))
    }
}

然后像这样使用它们

let logo = UText.welcomeLogo.centerInSuperview()
TextView

别名是 UTextView

// 已实现。待描述

Toggle

别名是 UToggle

// 已实现。待描述

属性

所有属性都可以声明方式设置,并且可以绑定到 UState

很多图层属性可以直接访问,并且具有方便的初始化器。

透明度 (Alpha)
UView().alpha(0)
UView().alpha($alphaState)
UView().alpha($boolState.map { $0 ? 1 : 0 })
背景 (Background)
UView().background(.red)
UView().background(0xff0000)
UView().background($colorState)
UView().background($boolState.map { $0 ? .red : .green })
边框 (Borders)

设置所有边的边框

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

设置特定边的边框

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

移除特定边的边框

.removeBorder(.top)
边界 (Bounds)
// implemented. to be described
抗压缩性 (Compression Resistance)
// implemented. to be described
圆角 (Corners)

设置所有角的圆角半径

UView().corners(10)
UView().corners($cornerRadiusState)

为特定角设置自定义圆角半径

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

通过较短边自动使视图的角变为圆形

UView().circle()
隐藏 (Hidden)
UView().hidden() // will set `true` by default
UView().hidden(true)
UView().hidden(false)
UView().hidden($hiddenState)
UView().hidden($stringState.map { $0.count > 0 })
拥抱优先级 (Hugging Priority)
// implemented. to be described
自身 (Itself)
// implemented. to be described
布局边距 (Layout Margin)
// to all sides
UView().layoutMargin(10)
// optional sides
UView().layoutMargin(top: 10)
UView().layoutMargin(left: 10, bottom: 5)
UView().layoutMargin(top: 10, right: 5)
// vertical and horizontal
UView().layoutMargin(x: 10, y: 5) // top: 5, left: 10, right: 10, bottom: 5
UView().layoutMargin(x: 10) // left: 10, right: 10
UView().layoutMargin(y: 5) // top: 5, bottom: 5
聚焦到下一个响应者或放弃焦点
// implemented. to be described
不透明度 (Opacity)
UView().opacity(0)
UView().opacity($alphaState)
UView().opacity($boolState.map { $0 ? 1 : 0 })
栅格化 (Rasterize)

栅格化图层,例如,为了更好的阴影性能

UView().rasterize() // true by default
UView().rasterize(true)
UView().rasterize(false)
阴影 (Shadow)
// to be described more

// and with mroe than one shadow
// and with state, expressableState
UView().shadow() // by default it's black, opacity 1, zero offset, radius 10
UView().shadow(.gray, opacity: 0.8, offset: .zero, radius: 5)
UView().shadow(0x000000, opacity: 0.8, offset: .zero, radius: 5)
抖动 (Shake)

您可以通过调用来抖动任何视图

UView().shake()

并且您可以自定义抖动效果

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

甚至可以创建一个扩展

import UIKitPlus

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

示例 (Examples)

示例应用在这里

示例 1

import UIKitPlus

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

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

示例 2

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 {
    @UState var email = ""
    @UState var password = ""

    override func buildUI() {
        super.buildUI()
        view.backgroundColor = .black
        body {
            UButton.back.onTapGesture { print("back tapped") }
            UText.welcome.text("Welcome").centerXInSuperview().topToSuperview(62, safeArea: true)
            UVStack {
                UTextField.welcome.text($email).placeholder("Email").keyboard(.emailAddress).content(.emailAddress)
                UTextField.welcome.text($password).placeholder("Password").content(.password).secure()
                UView().height(10) // just to add extra space
                UButton.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 😉
    }
}

您只需要几个扩展即可使其工作

// 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 UText {
    static var title: UText { UText().color(.white).font(.sfProMedium, 18) }
}
extension UTextField {
    static var welcome: UTextField {
        UTextField()
            .height(40)
            .background(.clear)
            .color(.black)
            .tint(.mainGreen)
            .border(.bottom, 1, .gray)
            .font(.sfProRegular, 16)
    }
}
extension UButton {
    static var back: UButton { UButton("backIcon").topToSuperview(64).leadingToSuperview(24) }
    static var bigBottomGreen: UButton {
        UButton()
            .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.