本篇文章將帶領(lǐng)大家一起學(xué)習(xí)SwiftUI中的ViewModifier帅霜,通過學(xué)習(xí)ViewModifier匆背,我們可以了解Swift中的@_functionBuilder
。
大家先看下邊這段代碼:
VStack {
Text("abc")
Spacer()
Text("def")
}
在SwiftUI中身冀,這樣的代碼太常見了钝尸,但大家有沒有思考過,在大括號(hào)中間搂根,放了幾個(gè)view珍促,這幾個(gè)view是如何添加到父view上的呢?
我們先看一個(gè)普通的函數(shù):
func test(_ content: () -> String) -> Void {
print(content())
}
這是一個(gè)很普通的函數(shù)剩愧,但是函數(shù)的參數(shù)猪叙,我們傳遞了一個(gè)閉包,接下來, 我們調(diào)用這個(gè)函數(shù):
Button("test") {
self.test {
"abc"
}
}
當(dāng)閉包作為最后一個(gè)參數(shù)時(shí)沐悦,我們可以像上邊這些寫代碼成洗,那么五督,我為什么要演示上邊的這個(gè)函數(shù)調(diào)用呢藏否?請(qǐng)大家再仔細(xì)看這段代碼:
VStack {
Text("abc")
Spacer()
Text("def")
}
大家明白了嗎? 上邊的閉包其實(shí)就是VStack的一個(gè)初始化函數(shù)的最后一個(gè)參數(shù)充包,跟上邊我們演示的函數(shù)沒什么兩樣副签。我們?cè)倮^續(xù)看看其函數(shù)定義:
/// A view that arranges its children in a vertical line.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct VStack<Content> : View where Content : View {
/// Creates an instance with the given `spacing` and Y axis `alignment`.
///
/// - Parameters:
/// - alignment: the guide that will have the same horizontal screen
/// coordinate for all children.
/// - spacing: the distance between adjacent children, or nil if the
/// stack should choose a default distance for each pair of children.
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
public typealias Body = Never
}
通過分析,我們可以發(fā)現(xiàn)以下幾點(diǎn):
- VStack是一個(gè)結(jié)構(gòu)體
- 其初始化函數(shù)的最后一個(gè)參數(shù)為
@ViewBuilder content: () -> Content
基矮,該函數(shù)與普通函數(shù)的區(qū)別在于前邊有一個(gè)@ViewBuilder
那么這個(gè)@ViewBuilder
是什么東西呢淆储?我們繼續(xù)看它的定義:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@_functionBuilder public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
/// unmodified.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Provides support for "if" statements in multi-statement closures, producing an `Optional` view
/// that is visible only when the `if` condition evaluates `true`.
public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View
/// Provides support for "if" statements in multi-statement closures, producing
/// ConditionalContent for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures, producing
/// ConditionalContent for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}
可以看出,ViewBuilder本身也是一個(gè)結(jié)構(gòu)體家浇,但是它用了@_functionBuilder
修飾本砰,那么@_functionBuilder
有什么用呢?
@_functionBuilder
能夠讓我們對(duì)函數(shù)做一層轉(zhuǎn)換钢悲,這是它最大的用處点额,我們舉個(gè)簡單的例子:
@_functionBuilder struct TestBuilder {
static func buildBlock(_ items: String...) -> [String] {
items
}
}
struct ContentView: View {
@State private var text = "ccc"
var body: some View {
VStack {
Button("test") {
self.test {
"a"
"b"
"c"
"d"
}
}
}
}
func test(@TestBuilder _ content: () -> [String]) -> Void {
print(content())
}
}
當(dāng)我們點(diǎn)擊按鈕后,可以打印出:
["a", "b", "c", "d"]
大家明白了嗎莺琳? 通過@_functionBuilder
还棱,我們就可以獲取函數(shù)中的變量,然后拿著這些數(shù)據(jù)做一些額外的事情惭等。
上邊的代碼珍手,是我們自己實(shí)現(xiàn)的一個(gè)builder,目的是把變量放到一個(gè)數(shù)組中辞做,那么ViewModifier做了什么事情呢琳要?
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View
}
...
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
很明顯,ViewBuilder把我們輸入的view最終轉(zhuǎn)成了TupleView秤茅,在上邊代碼中的最后一個(gè)extension中稚补,最多只能接受10個(gè)view,這也就是在SwiftUI中的容器類型最多可以放10個(gè)view的原因嫂伞。
當(dāng)然孔厉,我們?nèi)绻敕鸥嗟膙iew,可以通過Group或者ForEach來實(shí)現(xiàn)帖努。
我們?cè)偕钊胍稽c(diǎn)撰豺,大家看下邊的代碼:
struct ContentView: View {
@State private var hasText = false
@State private var show = false
var body: some View {
VStack {
Text("a")
if hasText {
Text("b")
}
if show {
Text("d")
} else {
Text("")
}
Text("c")
}
}
}
ViewBuilder為了支持閉包中的if表達(dá)式,特意擴(kuò)展了一些東西:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@_functionBuilder public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
/// unmodified.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Provides support for "if" statements in multi-statement closures, producing an `Optional` view
/// that is visible only when the `if` condition evaluates `true`.
public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View
/// Provides support for "if" statements in multi-statement closures, producing
/// ConditionalContent for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures, producing
/// ConditionalContent for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}
知道了這些知識(shí)后拼余,我們平時(shí)該如何使用ViewBuilder呢污桦?
struct ContentView: View {
@State private var hasText = false
@State private var show = true
var body: some View {
CustomView(Color.orange) {
Text("aaaa")
}
}
}
struct CustomView<T: View>: View {
let bgColor: Color
var content: T
init(_ bgColor: Color, @ViewBuilder _ content: () -> T) {
self.bgColor = bgColor
self.content = content()
}
var body: some View {
self.content
.background(self.bgColor)
}
}
目的是能夠開發(fā)出類似上邊代碼這樣的view, 可以為自定義的view擴(kuò)展其他的view匙监。
到目前為止凡橱,我們已經(jīng)了解了ViewBuilder的原理小作,我們還可以使用@_functionBuilder做一些更有趣的事情:
如果我們想在某個(gè)頁面中彈出一個(gè)Action,需要寫下邊這樣的代碼:
let alert = UIAlertController(
title: "Delete all data?",
message: "All your data will be deleted!",
preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Yes, Delete it All", style: .destructive) { (_) in
print("Deleting all data")
}
let moreOptionsAction = UIAlertAction(title: "Show More Options", style: .default) { (_) in
print("Show more options")
}
let cancelAction = UIAlertAction(title: "No, Don't Delete Anything", style: .cancel, handler: nil)
alert.addAction(deleteAction)
alert.addAction(moreOptionsAction)
alert.addAction(cancelAction)
present(alert, animated: true)
使用@_functionBuilder的黑魔法后稼钩, 我們的代碼編程這樣:
typealias RAlertActionHandler = () -> Void
protocol RAlertAction {
var title: String { get }
var style: UIAlertAction.Style { get }
var action: RAlertActionHandler { get }
}
struct DefaultAction: RAlertAction {
let title: String
let style: UIAlertAction.Style
let action: RAlertActionHandler
init(_ title: String, action: @escaping RAlertActionHandler = {}) {
self.title = title
self.style = .default
self.action = action
}
}
struct CancelAction: RAlertAction {
let title: String
let style: UIAlertAction.Style
let action: RAlertActionHandler
init(_ title: String, action: @escaping RAlertActionHandler = {}) {
self.title = title
self.style = .cancel
self.action = action
}
}
struct DestructiveAction: RAlertAction {
let title: String
let style: UIAlertAction.Style
let action: RAlertActionHandler
init(_ title: String, action: @escaping RAlertActionHandler = {}) {
self.title = title
self.style = .destructive
self.action = action
}
}
上邊代碼定義了幾種不同樣式的Action
@_functionBuilder
struct RAlertControllerBuilder {
static func buildBlock(_ components: RAlertAction...) -> [UIAlertAction] {
components.map { action in
UIAlertAction(title: action.title, style: action.style) { _ in
action.action()
}
}
}
}
// MARK:- UIAlertController
extension UIAlertController {
convenience init(title: String,
message: String,
style: UIAlertController.Style = .alert,
@RAlertControllerBuilder build: () -> [UIAlertAction]) {
let actions = build()
self.init(title: title, message: message, preferredStyle: style)
actions.forEach { self.addAction($0) }
}
}
這段代碼顾稀,把RAlertAction轉(zhuǎn)換成UIAlertAction,然后添加到UIAlertController中坝撑,有了上邊我們講解的知識(shí)静秆,大家應(yīng)該能夠理解這些代碼。
我們?cè)陂_發(fā)中這樣使用:
let alert = UIAlertController(
title: "Delete all data?",
message: "All your data will be deleted!") {
DestructiveAction("Yes, Delete it All") {
print("Deleting all data")
}
DefaultAction("Show More Options") {
print("showing more options")
}
CancelAction("No, Don't Delete Anything")
}
present(alert, animated: true)
重點(diǎn)是巡李,基于這些用法抚笔,我們可以開發(fā)出很多其他的Builders,再舉一個(gè)網(wǎng)上的例子:
NSAttributedString {
AText("Hello world")
.font(.systemFont(ofSize: 24))
.foregroundColor(.red)
LineBreak()
AText("with Swift")
.font(.systemFont(ofSize: 20))
.foregroundColor(.orange)
}
更多內(nèi)容侨拦,參考這個(gè)網(wǎng)站https://github.com/carson-katri/awesome-function-builders
總結(jié)
我們從SwiftUI中的VStack開始殊橙,學(xué)習(xí)了ViewBuilder的用法和原理,又學(xué)習(xí)了@_functionBuilder的用法狱从,最后我們舉了兩個(gè)例子來演示如何自定義函數(shù)Builder膨蛮。這些技術(shù)可以在Swift中做各種各樣的擴(kuò)展,全憑大家的想象力矫夯。