
置顶
菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
“不积跬步,无以至千里;不积小流,无以成江海”
继续
之前我们已经基本实现了接口的请求,本章我们来讨论如何封装一个自己的 View
。
通过观察我们可以发现,我们自己封装的 View
都是在以行的方式调用,与原始的 View
调用比较不一样
如图:

很多的 View
都是可以写内容,和指定某个参数是一个 View
的,那么我们是否也可以封装一个这样的View
呢?如图:

中间那个是我们传入的参数,头部和底部都是外面传入的,整个布局就已经是这样上中下三栏式了
希望不用每次这样布局的时候都写一大堆,直接调用,传入参数和顶部底部的东西即可。
这就很类似 vue
中的 默认插槽(default slot
) 和 命名插槽。
我们可以定义好布局、动画、样式等,再内容插入的地方,这样再调用起来就方便多了。
思考 + 踩坑
想到这个问题的时候第一反应自然是看看原生的view是怎么实现的。
command
+ 鼠标点击View
-> Jump to Definition
可以看到如下代码(省略了注释)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {
associatedtype Body : View
@ViewBuilder @MainActor var body: Self.Body { get }
}
这是一个 Protocol
,先暂时理解为其他语言中的 Interface
,即只能定义规范,不能定义实现
我们可以看到 View
里面定义了一个 @ViewBuilder
的参数,其他的先不管😂
同理,我们可以另起一个 View
进行尝试
果然得到了报错。。。

看报错的意思,这个 View
是个 Protocol
,所以要在前面加 any

直接点一手 Fix
又得到了新的报错:

大意为:any View
不能放到 View
里面。
实现
到了这个报错的地步就很明了了,既然不能是个 any View
,那就得是个实际的 View
,提前定义即可了。
但是很显然,我们不能定义一个新的 bottom:View
的 struct
出来,因为我们要传的就是这个 View
,如果提前定义好了,我们传什么?所以就引出了以下的写法:
// ExampleView.swift
import SwiftUI;
// <> 里面相当于我提前定义了一个View,但它还没有实现,以后用的
struct ExampleView<Top:View,Bottom:View>:View{
@State public var title:String;
// 这个 bottom 方法是个 ViewBuilder,返回的是一个 Bottom
@ViewBuilder public var bottom:()-> Bottom;
// 这个 top 方法也是个 ViewBuilder,返回的是一个 Top
@ViewBuilder public var top:() -> Top;
var body: some View{
top();
Spacer();
Text(title);
Spacer();
bottom();
}
}
这次没有报错,于是我们就可以愉快地对这个 View
进行调用了
// helloworldApp.swift
import SwiftUI
@main
struct helloworldApp: App {
var body: some Scene {
WindowGroup {
// 允许调用Toast,来自扩展
// IndexView()
// .enableToast()
// 直接跟在后面用花括号包裹的
// 默认是定义的里面的@ViewBuilder的第一个,本例中是 bottom
// 其他的都必须使用命名参数
ExampleView(title: "测试一个自己的View"){
Text("")
} top: {
Text("top content")
}
}
}
}
运行得到开头那张图的结果。
总结
- 看到报错或者警告的时候别忘了点开那个红点,说不定有
Fix
,看看官方建议怎么做,虽然有时候也不一定对😂 - 本文只是简单地展示了一下
View
封装的方式,这样我就可以写一些布局甚至页面的模板出来复用了。 - 官方原本的
Protocol
也是很好的例子,别忘了默认的一些视图和方法也是可以点开的,看看原本是怎么定义的。 -
Protocol
后面再讨论具体的意义和使用,不过想来和其他语言中的Interface
是差不多的。
欢迎关注公主号【思跃喵】,一起探讨。
网友评论