🚀❤️ 你会比以往任何时候都更喜欢 UIKIT ❤️🚀
一切皆有可能!
构建出色的响应式 UI 比 SwiftUI 还要简单,因为你已经知道一切。
支持实时预览。iOS9+。
Xcode 13.0+
Swift 5.5+
好心情
将以下行添加到你的 Podfile
pod 'UIKit-Plus', '~> 2.2.0'
在 Xcode 13.0+ 中,前往 File -> Swift Packages -> Add Package Dependency
并在其中输入此仓库的 URL
https://github.com/MihaelIsaev/UIKitPlus
自版本 2 以来,有很多优点和修复,并且你的项目看起来会更干净,因为不再有 AppDelegate 和 SceneDelegate,一切都在幕后,就像 SwiftUI 一样,但使用任何 AppDelegate/SceneDelegate 方法都非常明显且方便。
通过使用新的项目模板创建一个项目来查看它!
要支持低于 13 的 iOS 版本,你必须在 Build Settings
的 Other Linker Flags
中设置 -weak_framework SwiftUI
。
如果没有它,你的应用程序将在低于 13 的 iOS 版本上崩溃,因为它会尝试加载 SwiftUI 而无法成功。
为了简化 UIKitPlus 的使用,你可以下载我们的模板!
为此,请在控制台中运行以下命令
git clone https://github.com/MihaelIsaev/UIKitPlus.git
cp -R UIKitPlus/Templates ~/Library/Developer/Xcode/
rm -rf UIKitPlus
之后,你将能够转到 File -> New -> Project
并选择 UIKitPlus
应用程序! 🚀
💡创建项目后,你必须手动安装 UIKitPlus,无论是通过 Swift Package Manager 还是通过 CocoaPods
连同项目模板,你将获得文件模板 👍
在将视图添加到父视图之前提前声明所有约束。甚至可以通过标签。
Button("Click me").width(300).centerInSuperview()
以声明式方式构建一切。任何视图。任何控件。甚至图层、手势、颜色、字体等。
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)
对任何属性使用 @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" })
一切都很清楚。干净简洁的代码,没有魔法。
像在 SwiftUI 中一样声明子视图(但不要忘记我们仍然在 UIKit 中并使用自动布局)
body {
View1()
View2()
View3()
// btw it is NOT limited to 10
}
在扩展中声明视图或其样式。子类化视图。使用 OOP 的所有力量。
Diffable data-source(是的,iOS9+ 支持)。浅色/深色模式的动态颜色。可状态动画。响应性。
内置 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("Привет"))
自定义特征集合。
由 SwiftUI 提供的实时预览(仅自 macOS Catalina 起可用)。
我们遇到的唯一问题是,由于
UIKitPlus
和SwiftUI
中的视图名称相同,我们应该使用别名,例如UButton
代表Button
或UView
代表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
这只是一种在单个结构中创建多个预览的便捷方法
限制
rtl
和 language
属性只能设置为组,不能直接设置为预览#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
!
/// 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)
/// 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))
/// 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))
/// 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()
/// 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)
/// 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 })
/// 16pt to leading of superview
UView().leadingToSuperview(16)
/// all the same as with topToSuperview
/// -16pt to trailing of superview
UView().trailingToSuperview(-16)
/// all the same as with topToSuperview
/// -16pt to bottom of superview
UView().leadingToSuperview(-16)
/// all the same as with topToSuperview
/// right in center of superview horizontally
UView().centerXInSuperview()
/// 16pt from horizontal center of superview
UView().centerXToSuperview(16)
/// all the same as with topToSuperview
/// right in center of superview vertically
UView().centerYInSuperview()
/// 16pt from vertical center of superview
UView().centerYToSuperview(16)
/// all the same as with topToSuperview
/// 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
/// 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
/// 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()
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)
UView().leading(to: otherView)
/// all the same as for top(to:)
UView().trailing(to: otherView)
/// all the same as for top(to:)
UView().bottom(to: otherView)
/// all the same as for top(to:)
UView().left(to: otherView)
/// all the same as for top(to:)
UView().right(to: otherView)
/// all the same as for top(to:)
UView().centerX(to: otherView)
/// all the same as for top(to:)
UView().centerY(to: otherView)
/// all the same as for top(to:)
UView().center(to: otherView)
/// all the same as for top(to:)
UView().width(to: otherView)
/// all the same as for top(to:)
UView().height(to: otherView)
/// all the same as for top(to:)
/// just a convenient method to width&height
UView().equalSize(to: otherView)
/// all the same as for top(to:)
💡 提示:随时使用
UState
和基于设备类型的值
我们经常需要创建一些约束相互关联的视图 😃
经典方法是在外部某个地方创建一个带有视图的变量,如下所示
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
别名是
UView
View 可以使用空的初始化器创建
UView()
或者你可以在初始化时将子视图放入其中
UView {
UView()
UView()
}
或者你可以使用 inline
关键字包装一些视图,以便内部视图粘附到父视图的所有边缘
UView(inline: MKMapView())
你还可以通过调用 .body { ... }
方法将子视图添加到该父视图。甚至多次。
UView().body {
UView()
UVSpace(8)
UView()
}.body {
UView()
}.body {
UView()
UView()
UView()
}
// 已实现。待更详细描述
这真是一个奖励视图! :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)
}
// 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) }
}
这是一个简单的 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)
// 已实现。待描述
我最喜欢的功能。
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"))
只需创建一个专用的本地化文件(例如 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
// 已实现。待描述
// 已实现。待描述
// 已实现。待描述
别名是
UStackView
// 已实现。待描述
UStackView().axis(.vertical)
.alignment(.fill)
.distribution(.fillEqually)
.spacing(16)
别名是
UVStack
// 已实现。待更详细描述
与 StackView
相同,但具有预定义的轴并能够轻松添加排列的子视图
UVStack (
UText("hello world").background(.green),
UVSpace(16) // 16pt delimiter
UText("hello world").background(.red)
)
.spacing(10)
.alignment(.left)
.distribution(...)
// implemented. to be described
/// it is the same as VStack but it is combined with ScrollView
别名是
UHStack
// 已实现。待更详细描述
与 StackView
相同,但具有预定义的轴并能够轻松添加排列的子视图
UHStack (
UText("hello world").background(.green),
UHSpace(16) // 16pt delimiter
UText("hello world").background(.red)
)
.spacing(10)
.alignment(.left)
.distribution(...)
// implemented. to be described
/// it is the same as HStack but it is combined with ScrollView
/// just a horizontal delimiter
UHSpace(16)
/// alternatively
UView().width(16)
/// just a vertical delimiter
UVSpace(16)
/// alternatively
UView().height(16)
/// just a flexible space for stack views
USpace()
/// alternatively
UView()
// 已实现。待描述
别名是
UImage
// 待更详细描述
像这样声明资产图像
import UIKitPlus
extension Image {
static var welcomeBackground: UImage { return UImage("WelcomeBackground") }
}
然后像这样使用它们
let backgroudImage = UImage.welcomeBackground.edgesToSuperview()
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
// 已实现。待描述
别名是
UList
// implemented. to be described
also describe auto-DIFF with Identable models
// 已实现。待描述
// 已实现。待描述
// 已实现。待描述
// 已实现。待更详细描述
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)
别名是
USegmentedControl
// 已实现。待更详细描述
@UState var selectedItem = 0
USegmentedControl("One", "Two").select($selectedItem)
// or simply
USegmentedControl("One", "Two").select(0).changed { print("segment changed to \($0)") }
// 已实现。待描述
别名是
UStepper
// 已实现。待描述
别名是
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 }
别名是
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()
别名是
UTextView
// 已实现。待描述
别名是
UToggle
// 已实现。待描述
所有属性都可以声明方式设置,并且可以绑定到 UState
。
很多图层属性可以直接访问,并且具有方便的初始化器。
UView().alpha(0)
UView().alpha($alphaState)
UView().alpha($boolState.map { $0 ? 1 : 0 })
UView().background(.red)
UView().background(0xff0000)
UView().background($colorState)
UView().background($boolState.map { $0 ? .red : .green })
设置所有边的边框
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)
// implemented. to be described
// implemented. to be described
设置所有角的圆角半径
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()
UView().hidden() // will set `true` by default
UView().hidden(true)
UView().hidden(false)
UView().hidden($hiddenState)
UView().hidden($stringState.map { $0.count > 0 })
// implemented. to be described
// implemented. to be described
// 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
UView().opacity(0)
UView().opacity($alphaState)
UView().opacity($boolState.map { $0 ? 1 : 0 })
栅格化图层,例如,为了更好的阴影性能
UView().rasterize() // true by default
UView().rasterize(true)
UView().rasterize(false)
// 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)
您可以通过调用来抖动任何视图
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)
}
}
UView().tag(0)
UView().tint(.red)
UView().tint(0xff0000)
UView().tint($colorState)
UView().tint($boolState.map { $0 ? .red : .green })
// implemented. to be described
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)
}
}
}
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.