您需要使用一些带下划线前缀(表示它们不稳定)的API来读取ViewBuilder
中的视图.然后,您可以使用您自己的特征键,同样使用下划线前缀的API来读取视图特征.特征可以存储表示传递给您的自定义修改器的视图的AnyView
.
要从ViewBuilder
中提取视图,可以使用Swift Package View Extractor.你可以看看他们是如何做到这一点的-这并不是很多代码.
例如,让我们创建一个VStack
,它允许您使用自定义修饰符vstackButtonLabel
在其子视图的底部添加按钮.用户可以使用它来指定他们想要的按钮标签的视图.
struct VStackWithButtons<Content: View>: View {
@ViewBuilder let content: () -> Content
var body: some View {
VStack {
content()
HStack {
ExtractMulti(content) { views in
// "views" is a RandomAccessCollection
ForEach(views) { view in
// You can also access "view.id" here if needed
if let buttonLabel = view[VStackButtonLabelTrait.self] {
Button {
print("Do something")
} label: {
buttonLabel
}
}
}
}
}
}
}
}
VStackButtonLabelTrait
是一个_ViewTraitKey
:
struct VStackButtonLabelTrait: _ViewTraitKey {
static let defaultValue: AnyView? = nil
}
extension View {
func vstackButtonLabel<Content: View>(@ViewBuilder content: () -> Content) -> some View {
_trait(VStackButtonLabelTrait.self, AnyView(content()))
}
}
用法示例:
VStackWithButtons {
Text("This has no buttons")
Text("This has a button")
.vstackButtonLabel {
Label("Foo", systemImage: "globe")
}
Text("This also has a button")
.vstackButtonLabel {
Label("Bar", systemImage: "rectangle")
}
}
结果:
作为另一个例子,这里有一个非常简单的"选项卡视图"(如果你可以这样称呼它的话),它允许你使用 Select 器来 Select 选项卡.
struct ContentView: View {
@State var selectedTab = 0
var body: some View {
CustomTabView(selectedTab: $selectedTab) {
Text("Tab 1")
.id(0)
Text("Tab 2")
.id(1)
.customTabItem {
Label("Foo", systemImage: "globe")
}
Text("Tab 3")
.id(2)
.customTabItem {
Label("Bar", systemImage: "rectangle")
}
}
}
}
struct CustomTabView<Content: View, Selection: Hashable>: View {
@Binding var selectedTab: Selection
@ViewBuilder let content: () -> Content
var body: some View {
VStack {
ExtractMulti(content) { views in
ForEach(views) { view in
if view.id(as: Selection.self) == selectedTab {
view
}
}
}
Picker("Pick", selection: $selectedTab) {
ExtractMulti(content) { views in
ForEach(views) { view in
if let id = view.id(as: Selection.self) {
if let label = view[CustomTabItemTrait.self] {
label.tag(id)
} else {
Text("Unnamed").tag(id)
}
}
}
}
}
}
}
}
extension View {
func customTabItem<Content: View>(@ViewBuilder content: () -> Content) -> some View {
_trait(CustomTabItemTrait.self, AnyView(content()))
}
}
struct CustomTabItemTrait: _ViewTraitKey {
static let defaultValue: AnyView? = nil
}