一切皆有可能!
构建出色的响应式 UI,比使用 SwiftUI 还简单因为您已经知道所有内容。
有了.Live.预览. iOS9+。
需求
Xcode 13.0+
Swift 5.5+
好心情
安装
CocoaPods
使用将以下行添加到您的 Podfile 中
pod 'UIKit-Plus', '~> 2.1.2'
Swift Package Manager
使用在 Xcode 13.0+ 中,转到文件 -> Swift 包 -> 添加包依赖
并在此处输入此存储库的 URL
https://github.com/MihaelIsaev/UIKitPlus
重要的是!
自2.0版本以来,有很多优点和修复,您的项目看起来可能更简洁,因为没有再需要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
之后,您将能够转到 文件 -> 新建 -> 项目
并选择 UIKitPlus
应用!
💡 在创建项目后,您必须手动安装 UIKitPlus,无论是使用 Swift Package Manager 还是用 CocoaPods。
文件模板
除了项目模板外,您还将获得文件模板。
特点
1. 延迟约束
在将视图添加到superview之前提前声明所有的约束。甚至用标签。
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. 可重用和可扩展
在扩展中声明视图或其样式。子类化视图。使用面向对象所有强大的功能。
7. 所有现代功能
可区分的数据源(适用于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 版本起可用) 提供实时预览。
我们遇到的问题是,由于在
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
🔥
预览组这是在单个结构体内创建多个预览的便捷方式
限制
- 组内仅限 10 个预览
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
中心X
/// right in center of superview horizontally
UView().centerXInSuperview()
/// 16pt from horizontal center of superview
UView().centerXToSuperview(16)
/// all the same as with topToSuperview
中心Y
/// 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:)
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
和基于设备类型的值。
🔥
通过标签的相对约束
我们通常需要创建一些具有相互相关约束的视图
经典的办法是在视图外面创建一个变量,就像这样
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)
}
但如果不需要在 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)
}
甚至顺序也不重要
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)
例如,你想达到centerView
相对于secondView
的底部约束,可以这样
// 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
视图可以通过无参数的初始化器创建
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
控制视图
// 已实现。将介绍。
日期选择器
// 已实现。将介绍。
动态选择视图
// 已实现。将介绍。
堆叠视图
别名为
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()
HUD
// 已实现。将介绍。
图像
别名为
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
输入视图
// 已实现。将介绍。
列表
别名为
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 }
文本(也称为 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
// 已实现。将介绍。
切换
别名是
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
示例
示例 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.