【问题标题】:Make SwiftUI TabView wrap its content让 SwiftUI TabView 包装它的内容
【发布时间】:2021-11-16 16:19:33
【问题描述】:

是否可以制作包装其内容的分页TabView?我不知道内容的高度(它是一个调整大小以适合屏幕宽度的图像)所以我不能使用frame 修饰符。

我的代码如下所示:

ZStack(alignment: .topTrailing) {
    TabView {
        ForEach(data) { entry in
            VStack {
                entry.image
                    .resizable()
                    .scaledToFit()
                    .frame(maxWidth: .infinity)

                Text(entry.description)
            }
        }
    }
    .tabViewStyle(.page(indexDisplayMode: .never))

    Color.red
        .frame(width: 20, height: 20)
}

问题是TabView与屏幕一样大,PageIndicator被放置在屏幕的右上角而不是图像的右上角。坦克的任何建议。

编辑:

我添加了可重现的代码。 PageIndicator 被红色矩形取代。

struct Test: View {
    struct Entry: Identifiable {
        let image: Image
        let description: String

        var id: String { description }
    }

    let data = [
        Entry(image: Image(systemName: "scribble"), description: "image 1"),
        Entry(image: Image(systemName: "trash"), description: "image 2")
    ]

    var body: some View {
        ZStack(alignment: .topTrailing) {
            TabView {
                ForEach(data) { entry in
                    VStack {
                        entry.image
                            .resizable()
                            .scaledToFit()
                            .frame(maxWidth: .infinity)

                        Text(entry.description)
                    }
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .never))

            Color.red
                .frame(width: 20, height: 20)
        }
    }
}

【问题讨论】:

    标签: swiftui swiftui-tabview


    【解决方案1】:

    您的PageIndicator 未放置在图像上,因为您没有将它放在那里。您将其放置在VStack 之上的层中,该层恰好包含文本和可能比屏幕短的图像。如果你想要图像上的PageIndicator,那么你需要专门这样做。您没有提供Minimal, Reproducible Example,但做了类似的工作:

    TabView {
        ForEach(data) { entry in
            VStack {
                entry.image
                    .resizable()
                    .scaledToFit()
                    .overlay(
                        PageIndicator()
                    )
                    .frame(maxWidth: .infinity)
                    .overlay(
                        PageIndicator()
                    )
    
                Text(entry.description)
            }
        }
    }
    .tabViewStyle(.page(indexDisplayMode: .never))
    

    .overlay() 需要放在.frame() 之前,这样它才会停留在图像上,而不是覆盖.frame()

    编辑:

    根据 MRE 和评论,这里有一个替代解决方案,将 PageIndicator 与图像顶部对齐,但不随图像滚动。请注意,这不是一个完美的 MRE,因为图像高度不同,但这个解决方案实际上也考虑了这一点。最后,我在图像上添加了一个黄色背景,以表明事物已正确对齐。

    struct Test: View {
    
        struct Entry: Identifiable {
            let image: Image
            let description: String
            
            var id: String { description }
        }
        
        let data = [
            Entry(image: Image(systemName: "scribble"), description: "image 1"),
            Entry(image: Image(systemName: "trash"), description: "image 2")
        ]
        
        @State private var imageTop: CGFloat = 50
        
        var body: some View {
            ZStack(alignment: .topTrailing) {
                TabView {
                    ForEach(data) { entry in
                        VStack {
                            entry.image
                                .resizable()
                                .scaledToFit()
                                .frame(maxWidth: .infinity)
                                .background(Color.yellow)
                                .background(
                                    GeometryReader { imageProxy in
                                        Color.clear.preference(
                                            key: ImageTopInGlobal.self,
                                            // This returns the top of the image relative to the TabView()
                                            value: imageProxy.frame(in: .named("TabView")).minY)
                                    }
                                )
    
                            Text(entry.description)
                        }
                    }
                }
                // This gives a reference to another container for comparison
                .coordinateSpace(name: "TabView")
                .tabViewStyle(.page(indexDisplayMode: .never))
                VStack {
                    Spacer()
                        .frame(height: imageTop)
                    Color.red
                        .frame(width: 20, height: 20)
                }
            }
            // You either have to ignore the safe area or account for it with regard to the TabView(). This was simpler.
            .edgesIgnoringSafeArea(.top)
            .onPreferenceChange(ImageTopInGlobal.self) {
                imageTop = $0
            }
        }
    }
    
    private extension Test {
        struct ImageTopInGlobal: PreferenceKey {
            static let defaultValue: CGFloat = 0
            
            static func reduce(value: inout CGFloat,
                               nextValue: () -> CGFloat) {
                value = nextValue()
            }
        }
    }
    

    最终编辑:

    针对最后一条评论,我的回答是不合格。我认为没有任何方法可以让TabView 拥抱它的内容。 (我使用术语 wrap 来表示拥抱的原始问题,因为 TabView 总是“包装”其内容。)如果您尝试使用首选项键,TabView 会崩溃。必须设置一个 minHeightheight 来防止这种情况,这违背了拥抱尝试的目的。

    【讨论】:

    • 嗨,不,这不起作用。我需要 PageIndicator 位于 TabView 上方,它不能随内容滚动。我将提供可重现的示例。
    • 我已经添加了示例。如果你运行它,你会看到当用户向右或向左滑动时红色矩形不会移动——这就是我需要的。我的问题是我需要向下移动矩形,使其与图像的右上角对齐(图像都具有相同的纵横比)。而这是我无法实现的。
    • 添加到答案中。
    • 完美 :) 我想我原来的问题的答案 - 是否可以制作包含其内容的分页 TabView? - 没有。您可以将此添加到您的答案中以便我接受吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 2021-07-06
    • 1970-01-01
    相关资源
    最近更新 更多