swift 如何在自定义视图中读取修饰符子元素?

w6mmgewl  于 12个月前  发布在  Swift
关注(0)|答案(2)|浏览(128)

如何在自定义视图中读取子元素中使用的modifier的值,我想达到类似TabView的效果?

struct TabItem: View {
    var body: some View {
        TabView {
            View1()
                .tabItem {
                    Label("Menu", systemImage: "list.dash")
                }

            View2()
                .tabItem {
                    Label("Order", systemImage: "square.and.pencil")
                }
        }
    }
}

字符串
我希望父视图(即我的CustomView)读取子元素是否有自定义修饰符(例如.inCustomView),并从中读取内容,并基于它创建按钮或其他内容,就像TabView一样

ia2d9nvy

ia2d9nvy1#

你需要使用一些带下划线前缀的API来读取ViewBuilder中的视图。然后你可以使用你自己的trait键来读取视图trait,同样使用带下划线前缀的API。trait可以存储一个AnyView,代表传递给你的自定义修饰符的视图。
要从ViewBuilder中提取视图,你可以使用Swift包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")
        }
}


测试结果:
x1c 0d1x的数据
作为另一个例子,这里有一个非常简单的“选项卡视图”(如果你可以这样称呼它的话),它允许你使用选择器来选择选项卡。

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
}

np8igboo

np8igboo2#

若要实现所需的效果,其中父视图CustomView可以读取其子元素中使用的修饰符的值,可以使用以下方法:
1.创建自定义TabItem修改器:

struct CustomTabItem<Content: View>: ViewModifier {
    let content: Content

    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { geometry in
                    Color.clear.preference(key: CustomTabItemPreferenceKey.self, value: geometry.size)
                }
            )
    }
}

extension View {
    func customTabItem<Content: View>(@ViewBuilder content: () -> Content) -> some View {
        modifier(CustomTabItem(content: content()))
    }
}

struct CustomTabItemPreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

字符串
1.在CustomView中,添加一个首选项读取器以读取自定义选项卡项的大小:

struct CustomView: View {
    @Preference var customTabItemSize: CGSize?

    var body: some View {
        VStack {
            if let customTabItemSize = customTabItemSize {
                // Create a button or something else based on the size of the custom tab item
                Button(action: {}) {
                    Text("Custom Tab Item")
                }
                .frame(width: customTabItemSize.width, height: customTabItemSize.height)
            }

            // Your other content
        }
    }
}


1.在子视图中,使用customTabItem修饰符将自定义修饰符应用于选项卡项:

struct View1: View {
    var body: some View {
        Text("View 1")
            .customTabItem {
                Label("Menu", systemImage: "list.dash")
            }
    }
}

struct View2: View {
    var body: some View {
        Text("View 2")
            .customTabItem {
                Label("Order", systemImage: "square.and.pencil")
            }
    }
}


通过这种方法,CustomView可以读取其子元素中使用的自定义选项卡项的大小,并根据该信息创建按钮或其他内容。
我希望这对你有帮助!

相关问题