该项目处于积极开发状态,直到 1.0.0
在 Xcode10+ 现在以 SwiftUI 类似的方式构建 UI,适用于 iOS9 及更高版本!
// NOTE:
// For now it's written for Swift 4.2-5.0
// but closer to 5.1 release it will support 5.1 features like `@State` and `@Binding`
// and became look more like SwiftUI cause `functionBuilder` became available.
通过给予⭐️来支持这个库
主要功能
您可以在任何地方构建包含所有约束(甚至到其他观点)的视图,然后一旦您将其添加到父视图中,所有约束都将被激活。
重用视图非常简单,只需在扩展中声明它们即可!
非常简短的介绍
// black 100x100 view in center of future superview
lazy var view1 = View().background(.black).size(100).centerInSuperview()
// red 30x20 view in horizontal center of future superview and with vertical spacing to view1
lazy var view2 = View().background(.red)
.size(30, 20)
.centerXInSuperview()
// yes! you can declare constraints before adding to superivew
.top(to: .bottom, of: view1, 16)
// view with view1 and view2 as subviews
let awesomeView = View.subviews { [view1, view2] }
func viewDidLoad() {
super.viewDidLoad()
view.addSubview(awesomeView)
// and yes! you can reach and change declared constraints easily!
UIView.animate(duration: 0.5) {
view2.centerX = 30
view2.outer[.top, view1] = 16
awesomeView.layoutIfNeeded()
}
}
长介绍
import UIKit
import UIKitPlus
// Just feel how easy you could build & declare your views
// with all needed constraints, properties and actions
// even before adding them to superview!
class LoginViewController: ViewController {
lazy var backButton = Button.back.tapAction { print("back tapped") }
lazy var titleLabel = Label.welcome.text("Welcome").centerXInSuperview().topToSuperview(62)
lazy var contentView = View.subviews { [fieldsView] }
.edgesToSuperview(top: 120, leading: 16, trailing: -16, bottom: 0)
.background(.white)
.corners(20, .topLeft, .topRight)
lazy var fieldsView = VStackView { [emailField, passwordField, signInButton] }
.edgesToSuperview(top: 10, leading: 8, trailing: -8)
// NOTE: WrapperView needed just to add padding since we're using these views inside VStackView
lazy var emailField = WrapperView {
TextField().welcome.placeholder("Email").keyboard(.emailAddress).content(.emailAddress)
}.padding(x: 10)
lazy var passwordField = WrapperView {
TextField.welcome.placeholder("Password").content(.password).secure()
}.padding(x: 10)
lazy var signInButton = WrapperView {
Button.bigBottomGreen.title("Sign In").tapAction(signIn)
}.padding(top: 10, left: 16, right: 16)
override func loadView() {
super.loadView()
view.backgroundColor = .black
view.addSubview(backButton, titleLabel, contentView)
}
func signIn() {
guard let email = emailField.innerView.text,
let password = passwordField.innerView.text else { return }
// do an API call to your server with CodyFire 😉
}
}
就这样!是的!您只需几个扩展即可使之工作
// 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 Label {
static var title: Label { return Label().color(.white).font(.sfProMedium, 18) }
}
extension TextField {
static var welcome: TextField {
return TextField().height(40)
.background(.clear)
.color(.black)
.tint(.mainGreen)
.border(.bottom, 1, .gray)
.font(.sfProRegular, 16)
}
}
extension Button {
static var back: Button { return Button("backIcon").topToSuperview(64).leadingToSuperview(24) }
static var bigBottomGreen: Button {
return Button().color(.white)
.font(.sfProMedium, 15)
.background(.green)
.height(50)
.circle()
.shadow(.gray, opacity: 1, offset: .init(width: 0, height: -1), radius: 10)
}
}
// PRO-TIP2:
// I'd suggest you to use extensions for everything: fonts, images, labels, buttons, colors, etc.
顺便说一句,用我们的 Example
项目的示例与 Playground
玩玩吧
// - you need CocoaPods installed on your Mac
// - clone or download this repo
// - go to Example folder in terminal and execute `pod install`
// - go to Example folder in Finder and open Example.xcworkspace
// - Find Example playground at the left top corner in Xcode and start playing
好奇?
安装
CocoaPods
使用将以下行添加到您的 Podfile 中
pod 'UIKit-Plus', '~> 0.14.1'
Swift Package Manager
使用在 Xcode11 中,前往 文件 -> Swift 包 -> 添加包依赖
并在此处输入此仓库的 URL
https://github.com/MihaelIsaev/UIKitPlus
Carthage
使用尚不支持。请随意发送 PR 以支持此功能。
使用方法
import UIKit
import UIKitPlus
快捷表
UIKitPlus | UIKit | |
---|---|---|
视图 | UIView | |
WrapperView | UIView | |
滚动视图 | UIScrollView | |
集合视图 | UICollectionView | |
表格视图 | UITableView | |
图片 | UIImageView | |
按钮 | UIButton | |
标签 | UILabel | |
文本框 | UITextField | |
分段控制器 | UISegmentedControl | |
视觉效果视图 | UIVisualEffectView | |
堆叠视图 | UIStackView | |
水平堆叠视图 | UIStackView | |
垂直堆叠视图 | UIStackView | |
验证码视图 | ||
AttributedString(又称AttrStr) | NSAttributedString | |
视图控制器 | UIViewController | |
导航控制器 | UINavigationController | |
表单控制器 | ||
日期选择器 | UIDatePicker | |
步进器 | UIStepper | |
滑块 | UISlider | |
开关 | UISwitch |
视图
这是一个简单的视图,可以通过声明式方式来自定义它。
View().background(.red).shadow().edgesToSuperview()
您还可以使用预定义的子视图来初始化它。
View.subviews {
let avatar = Image("some").size(100)
// stick to top, leading and trailing of superview
.edgesToSuperview(top: 0, leading: 0, trailing: 0)
// stick top to bottom of avatar view with 16pt
let name = Label("John Smith").top(to: .bottom, of: avatar, 8)
// stick to leading, trailing and bottom of superview
.edgesToSuperview(leading: 0, trailing: 0, bottom: 0)
return [avatar, name]
}
WrapperView
这是一个简单的View
,但具有初始化内部视图的能力。
WrapperView {
View().background(.red).shadow()
}.background(.green).shadow()
并且您可以在这里指定内部视图的右边距。
// to the same padding for all sides
WrapperView {
View()
}.padding(10)
// or to specific padding for each side
WrapperView {
View()
}.padding(top: 10, left: 5, right: 10, bottom: 5)
// or even like this
WrapperView {
View()
}.padding(top: 10, right: 10)
ScrollView
目前没有什么有趣的功能,但您可以在声明式方式中指定一些设置。
ScrollView().paging(true).scrolling(false).hideIndicator(.horizontal)
ScrollView().paging(true).scrolling(false).hideAllIndicators()
ScrollView().contentInset(.zero)
ScrollView().contentInset(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().contentInset(top: 10, bottom: 10)
ScrollView().scrollIndicatorInsets(.zero)
ScrollView().scrollIndicatorInsets(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().scrollIndicatorInsets(top: 10, bottom: 10)
CollectionView
目前没有什么有趣的功能。
CollectionView().paging(true)
TableView
目前没有什么有趣的功能。
Image
Image("someImage").mode(.scaleAspectFill).clipsToBounds(true)
Button
Button()
Button("Tap me")
Button().title("Tap me") // useful if you declared Button from extension like below
Button.mySuperButton.title("Tap me")
设置背景和突出状态下的背景。
Button("Tap me").background(.white).backgroundHighlighted(.darkGray)
设置不同状态下的标题颜色。
Button("Tap me").color(.black).color(.lightGray, .disabled)
从已声明的标识符或使用系统字体设置一些字体。
Button("Tap me").font(v: .systemFont(ofSize: 15))
Button("Tap me").font(.sfProBold, 15)
添加图片。
Button("Tap me").image(UIImage(named: "cat"))
Button("Tap me").image("cat")
您可以轻松处理点击事件。
Button("Tap me").tapAction { print("button tapped") }
Button("Tap me").tapAction { button in
print("button tapped")
}
或者像这样
func tapped() { print("button tapped") }
Button("Tap me").tapAction(tapped)
func tapped(_ button: Button) { print("button tapped") }
Button("Tap me").tapAction(tapped)
标签
它可以初始化为 String
或任意数量的 AttributedString
Label("hello 👋 ")
Label().text("hello") // useful if declare label in extension like below
Label.mySuperLabel.text("hello")
Label(AttributedString("hello").foreground(.red), AttributedString("world").foreground(.green))
从已声明的标识符或使用系统字体设置一些字体。
Label("hello").font(v: .systemFont(ofSize: 15))
Label("hello").font(.sfProBold, 15)
设置文本颜色
Label("hello").color(.red)
设置文本对齐
Label("hello").alignment(.center)
设置行数
Label("hello").lines(1)
Label("hello\nworld").lines(0)
Label("hello\nworld").lines(2)
Label("hello\nworld").multiline()
文本框
TextField()
TextField("some text")
TextField().text("some text")
TextField.mySuperDuperTextField.text("some text")
从已声明的标识符或使用系统字体设置一些字体。
TextField().font(v: .systemFont(ofSize: 15))
TextField().font(.sfProBold, 15)
设置文本颜色
TextField().color(.red)
设置文本对齐
TextField().alignment(.center)
占位符
TextField().placeholder("email")
// or use AttributedString to make it colored
TextField().placeholder(AttributedString("email").foreground(.green))
安全类型
TextField().secure()
轻松从字段中删除任何文本
TextField().cleanup()
设置键盘和内容类型
TextField().keyboard(.emailAddress).content(.emailAddress)
设置代理
TextField().delegate(self)
或以声明方式获取所需的事件
TextField().shouldBeginEditing { tf in return true }
.didBeginEditing { tf in }
.shouldEndEditing { tf in return true }
.didEndEditing { tf in }
.shouldChangeCharacters { tf, range, replacement in return true }
.shouldClear { tf in return true }
.shouldReturn { tf in return true }
.editingDidBegin { tf in }
.editingChanged { tf in }
.editingDidEnd { tf in }
分段控制
SegmentedControl("One", "Two").select(1).changed { print("segment changed to \($0)") }
视觉效果视图
VisualEffectView(.darkBlur)
VisualEffectView(.lightBlur)
VisualEffectView(.extraLightBlur)
// iOS10+
VisualEffectView(.prominent)
VisualEffectView(.regular)
为您的自定义效果创建扩展,以便像上面示例一样轻松使用
extension UIVisualEffect {
public static var darkBlur: UIVisualEffect { return UIBlurEffect(style: .dark) }
}
堆叠视图
StackView().axis(.vertical)
.alignment(.fill)
.distribution(.fillEqually)
.spacing(16)
水平堆叠视图
与 StackView
相同,但具有预定义的轴和轻松添加排列视图的能力
HStackView (
Label("hello world").background(.green),
Label("hello world").background(.red)
).spacing(10)
VStackView
与 StackView
相同,但具有预定义的轴和轻松添加排列视图的能力
VStackView (
Label("hello world").background(.green),
Label("hello world").background(.red)
).spacing(10)
VerificationCodeView
这真是一个bonus视图!: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)
}
任何视图
背景
View().background(.red)
View().background(0xff0000)
色调
View().tint(.red)
View().tint(0xff0000)
角落
设置所有角落的半径
View().corners(10)
设置特定角落的定制半径
View().corners(10, .topLeft, topRight)
View().corners(10, .topLeft, .bottomRight)
View().corners(10, .topLeft, topRight, .bottomLeft, .bottomRight)
通过较小的边自动将视图的角落设置为圆角
View().circle()
边框
设置所有边的边框
View().border(1, .black)
View().border(1, 0x000)
在特定边上设置边框
View().border(.top, 1, .black)
View().border(.left, 1, .black)
View().border(.right, 1, .black)
View().border(.bottom, 1, .black)
从特定边移除边框
.removeBorder(.top)
Alpha
View().alpha(0)
不透明度
View().opacity(0)
隐藏
View().hidden() // true by default
View().hidden(true)
View().hidden(false)
栅格化
为了更好地实现阴影性能,可以将图层栅格化,例如:
View().rasterize() // true by default
View().rasterize(true)
View().rasterize(false)
阴影
View().shadow() // by default it's black, opacity 1, zero offset, radius 10
View().shadow(.gray, opacity: 0.8, offset: .zero, radius: 5)
View().shadow(0x000000, opacity: 0.8, offset: .zero, radius: 5)
晃动
您只需调用即可晃动任何视图:
View().shake()
您还可以自定义晃动效果
View().shake(values: [-20, 20, -20, 20, -10, 10, -5, 5, 0],
duration: 0.6,
axis: .horizontal,
timing: .easeInEaseOut)
View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
duration: 0.6,
axis: .horizontal,
timing: .easeInEaseOut)
甚至创建一个扩展
import UIKitPlus
extension DeclarativeProtocol {
func myShake() {
View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
duration: 0.6,
axis: .horizontal,
timing: .easeInEaseOut)
}
}
AttributedString
您可以轻松以声明式方式创建具有属性的字符串
AttributedString("hello").background(.gray)
.foreground(.red)
.font(.sfProBold, 15)
.paragraphStyle(.default)
.ligature(1)
.kern(1)
.strikethroughStyle(1)
.underlineStyle(.patternDash)
.strokeColor(.purple)
.strokeWidth(1)
.shadow()
// or .shadow(offset: .zero, blur: 1, color: .lightGray)
.textEffect("someEffect")
.attachment(someAttachment)
.link("http://github.com")
.baselineOffset(1)
.underlineColor(.cyan)
.strikethroughColor(.magenta)
.obliqueness(1)
.expansion(1)
.glyphForm(.horizontal)
.writingDirection(.rightToLeft)
约束
大小
您可以通过传递 宽度
和 高度
值来设置视图大小,如下所示
View().size(100, 200)
或者通过传递单个值来设置正方形的大小
View().size(100)
或者视图的大小可以等于其他视图的大小,因此当您更改一个视图的大小时,另一个视图也会更改其大小
let view1 = View().size(100, 200)
let view2 = View().equalSize(to: view1)
当然,您可以只指定宽度或高度,或者分别通过单独的方法指定两者
View().width(100)
View().height(200)
View().width(100).height(200)
父视图
您的视图可以粘附到其父视图的任何一边,甚至所有边
// this way it will stick with 0 constant value
View().edgesToSuperview()
// this way it will stick with 10 constant value for all sides
View().edgesToSuperview(10)
// also you could specify some values manually, but all the rest values are 0 by default
View().edgesToSuperview(top: 16, leading: 16, trailing: -16)
View().edgesToSuperview(trailing: -16, bottom: -16)
您可以将视图粘附到父视图的一边,如下所示
// empty argument means 0 constant value
View()topToSuperview()
View()topToSuperview(16)
View()leadingToSuperview()
View()leadingToSuperview(16)
View()trailingToSuperview()
View()trailingToSuperview(16)
View()bottomToSuperview()
View()bottomToSuperview(16)
View()centerXToSuperview()
View()centerXToSuperview(16)
View()centerYToSuperview()
View()centerYToSuperview(16)
View()widthToSuperview()
View()heightToSuperview()
相对位置
您的视图的任意一边也可以粘附到其他视图的任意一边
// Sides to superview
View().top(to: .bottom, of: someView, 16) // stick view's top to someView`s bottom with 16pt (by default 0pt)
View().leading(to: .trailing, of: someView)
View().trailing(to: .leading, of: someView)
View().bottom(to: .top, of: someView)
// Center to superview
View().centerX(to: .centerX, of: someView)
View().centerY(to: .centerY, of: someView)
// Dimension Superview
View().width(to: .width, of: someView)
View().height(to: .height, of: someView)
或者这样
// Sides to superview
View().edge(.top, toSuperview: someView, .bottom)
View().edge(.leading, toSuperview: someView, .trailing)
View().edge(.trailing, toSuperview: someView, .leading)
View().edge(.bottom, toSuperview: someView, .top)
// Center to superview
View().edge(.centerX, toSuperview: someView, .centerX)
View().edge(.centerY, toSuperview: someView, .centerY)
// Dimension Superview
View().edge(.width, toSuperview: someView, .width)
View().edge(.height, toSuperview: someView, .height)
要为两个相对视图创建约束
// Sides to another views
View().spacing(.leading, to: relativeView, toSide: .trailing, 16) // last parameter is optional, 0 by default
View().spacing(.trailing, to: relativeView, toSide: .leading)
View().spacing(.top, to: relativeView, toSide: .bottom)
View().spacing(.bottom, to: relativeView, toSide: .top)
// Center to another relative views
View().center(.x, to: relativeView, toSide: .x)
View().center(.y, to: relativeView, toSide: .y)
// Dimension Relative
View().dimension(.width, to: relativeView, toSide: .width)
View().dimension(.height, to: relativeView, toSide: .height)
居中
您的视图可以位于其父视图的中央
View().centerInSuperview() // exact center
View().centerInSuperview(10) // exact center +10 by x-axis, and +10 by y-axis
View().centerInSuperview(x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis
或者可以在其他视图的中央
View().center(to: anotherView) // exact center
View().center(to: anotherView, 10) // exact center +10 by x-axis, and +10 by y-axis
View().center(to: anotherView, x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis
直接访问约束
好的,让我们想象一下,你有一个与父视图粘合的视图
let view = View().edgesToSuperview()
现在,你的视图对父视图有顶部、leading(左边缘)、trailing(右边缘)和底部的约束,例如,你想改变top
约束,可以这样操作
view.top = 16
或者
view.declarativeConstraints.top?.constant = 16
所有视图的约束都可以用同样的方式处理,所以你可以改变它们,甚至可以设置它们为nil
来删除它们。
另一种情况,如果你有一个设置了相对于另一个视图的约束的视图
let centerView = View().background(.black).size(100).centerInSuperview()
let secondView = View().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
布局边距
// to all sides
View().layoutMargin(10)
// optional sides
View().layoutMargin(top: 10)
View().layoutMargin(left: 10, bottom: 5)
View().layoutMargin(top: 10, right: 5)
// vertical and horizontal
View().layoutMargin(x: 10, y: 5) // top: 5, left: 10, right: 10, bottom: 5
View().layoutMargin(x: 10) // left: 10, right: 10
View().layoutMargin(y: 5) // top: 5, bottom: 5
安全区域
你可以在任何视图上安全地不使用#available(iOS 11.0, *)
检查来获取safeArea
someView.safeArea.topAnchor
约束值
任何约束值都可以设置为CGFloat
,或者与Relation
一起,甚至包含Multiplier
。
// just equal to 10
View().leading(to: .trailing, of: anotherView, 10)
// greaterThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, >=10)
// lessThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, <=10)
// equal to 10 with 1.5 multiplier
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5)
// equal to 10 with 1.5 multiplier and 999 priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! 999)
// equal to 10 with 1.5 multiplier and `.defaultLow` priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! .defaultLow)
// equal to 10 with 999 priority
View().leading(to: .trailing, of: anotherView, 10 ! 999)
颜色
使用UIKitPlus,你可以通过在前面加上0x
前缀来直接使用十六进制颜色,例如,使用0x000
表示黑色或使用0xfff
表示白色。
扩展
字体
将自定义字体添加到项目中,然后按照以下方式进行声明
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")
}
然后像这样使用它们
Button().font(.sfProMedium, 15)
颜色
像这样声明自定义颜色
import UIKit
import UIKitPlus
extension UIColor {
static var mainBlack: UIColor { return .black }
static var otherGreen: UIColor { return 0x3D7227.color } // 61 114 39
}
然后像这样使用它们
Label("Hello world").color(.otherGreen).background(.mainBlack)
按钮
像这样声明自定义按钮
import UIKitPlus
extension Button {
static var bigBottomWhite: Button {
return Button().color(.darkGray).color(.black, .highlighted).font(.sfProMedium, 15).background(.white).backgroundHighlighted(.lightGray).circle()
}
static var bigBottomGreen: Button {
return Button().color(.white).font(.sfProMedium, 15).background(.mainGreen).circle()
}
}
然后像这样使用它们
Button.bigBottomWhite.size(300, 50).bottomToSuperview(20).centerInSuperview()
标签
像这样声明自定义属性标签
import UIKitPlus
extension Label {
static var welcomeLogo: Label {
return .init(AttributedString("My").foreground(.white).font(.sfProBold, 26),
AttributedString("App").font(.sfProBold, 26))
}
}
然后像这样使用它们
let logo = Label.welcomeLogo.centerInSuperview()
图片
像这样声明资源图片
import UIKitPlus
extension Image {
static var welcomeBackground: Image { return Image("WelcomeBackground") }
}
然后像这样使用它们
let backgroudImage = Image.welcomeBackground.edgesToSuperview()
子视图
在一行中添加子视图
view.addSubview(view1, view2, view3)