如何制作可滚动的选项卡视图?

问题描述 投票:0回答:1

由于选项卡视图无法放入滚动视图内,我尝试使用新的 iOS 17 方法来使用滚动视图创建自定义选项卡。在我下面的代码中,如果您注释掉标记为“不起作用”的第一个 VStack 并取消注释第二个可用的 VStack 视图,您将看到我的自定义选项卡视图功能良好。

但是,我想制作一个配置文件视图,其中包含标题、向上滚动的中间内容(不固定)、固定在顶部的栏,然后是选项卡视图。这样做会导致我的自定义选项卡视图失败。

我找不到任何具有在滚动视图中运行的自定义选项卡视图的网站,所以我认为解决这个问题很重要。

import SwiftUI

#Preview(body: {
    SwiftUIView()
})

struct SwiftUIView: View {
    @State private var selectedTab: TabProfile?
    @State private var tabProgress: CGFloat = 0
    @Environment(\.colorScheme) var colorScheme
    
    var body: some View {
        
        //----- THIS DOESNT WORK
        
        VStack(alignment: .leading){
            Color.red.frame(height: 100)
                .overlay {
                    Text("Header").foregroundStyle(.white)
                        .offset(y: 20).bold()
                }
            
            ScrollView {
                LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
                    
                    Color.blue.frame(height: 100)
                        .overlay {
                            Text("Some content").foregroundStyle(.white).bold()
                        }
                    
                    Section(header: buttonTab()) {
                        rcontent()
                    }
                }
            }
            .scrollIndicators(.hidden)
        }
        .ignoresSafeArea()
        
        
        
        //-----THIS WORKS---------
        
//        VStack {
//            buttonTab()
//            
//            rcontent()
//        }
    }
    func rcontent() -> some View {
        GeometryReader {
            let size = $0.size
            
            ScrollView(.horizontal) {
                LazyHStack(spacing: 0) {
                    SampleView(.purple)
                        .id(TabProfile.hustles)
                        .containerRelativeFrame(.horizontal)
                    
                    SampleView(.red)
                        .id(TabProfile.jobs)
                        .containerRelativeFrame(.horizontal)
                    
                    SampleView(.blue)
                        .id(TabProfile.likes)
                        .containerRelativeFrame(.horizontal)
                    
                    SampleView(.green)
                        .id(TabProfile.sale)
                        .containerRelativeFrame(.horizontal)
                    
                    SampleView(.orange)
                        .id(TabProfile.question)
                        .containerRelativeFrame(.horizontal)
                }
                .scrollTargetLayout()
                .offsetX { value in
                    let progress = -value / (size.width * CGFloat(TabProfile.allCases.count - 1))
                    tabProgress = max(min(progress, 1), 0)
                }
            }
            .scrollPosition(id: $selectedTab)
            .scrollIndicators(.hidden)
            .scrollTargetBehavior(.viewAligned)
            .scrollClipDisabled()
        }
    }
    func buttonTab() -> some View {
        HStack(spacing: 0) {
            ForEach(TabProfile.allCases, id: \.rawValue) { tab in
                HStack(spacing: 10) {
                    Text(tab.rawValue)
                        .font(.callout)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .contentShape(.capsule)
                .onTapGesture {
                    UIImpactFeedbackGenerator(style: .light).impactOccurred()
                    withAnimation(.snappy) {
                        selectedTab = tab
                    }
                }
            }
        }
        .tabMask(tabProgress, tabCount: TabProfile.allCases.count)
        .padding(.horizontal, 8)
        .background {
            GeometryReader {
                let size = $0.size
                let capusleWidth = size.width / CGFloat(TabProfile.allCases.count)
                ZStack(alignment: .leading){
                    RoundedRectangle(cornerRadius: 0)
                        .fill(.gray)
                        .frame(height: 1)
                        .offset(y: 40)
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.blue)
                        .frame(width: capusleWidth, height: 2)
                        .offset(x: tabProgress * (size.width - capusleWidth), y: 40)
                }
            }
        }
    }
    @ViewBuilder
    func SampleView(_ color: Color) -> some View {
        ScrollView(.vertical) {
            LazyVGrid(columns: Array(repeating: GridItem(), count: 2), content: {
                ForEach(1...10, id: \.self) { _ in
                    RoundedRectangle(cornerRadius: 15)
                        .fill(color.gradient)
                        .frame(height: 150)
                }
            })
            .padding(15)
        }
        .scrollIndicators(.hidden)
        .scrollClipDisabled()
        .mask {
            Rectangle()
                .padding(.bottom, -100)
        }
    }
}

enum TabProfile: String, CaseIterable {
    case hustles = "Hustles"
    case jobs = "Jobs"
    case likes = "Likes"
    case sale = "4Sale"
    case question = "???"
}
swift swiftui
1个回答
-1
投票

这是我的解决方案:

import SwiftUI

#Preview(body: {
    SwiftUIView()
})

struct SwiftUIView: View {
    @State private var selectedTab: TabProfile?
    @State private var tabProgress: CGFloat = 0
    @Environment(\.colorScheme) var colorScheme
    let counts: [TabProfile.RawValue : Int] =  ["Hustles" : 20, "Jobs" : 2, "Likes" : 20]
    
    var body: some View {
        VStack(alignment: .leading){
            Color.red.frame(height: 100)
                .overlay {
                    Text("Header").foregroundStyle(.white)
                        .offset(y: 20).bold()
                }
            GeometryReader {
                let size = $0.size
                
                ScrollViewReader(content: { proxy in
                    ScrollView {
                        LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
                            
                            Color.blue.frame(height: 100)
                                .overlay {
                                    Text("Some content").foregroundStyle(.white).bold()
                                }
                                
                            
                            Section(header: buttonTab().id("top")) {
                                ScrollView(.horizontal) {
                                    LazyHStack(alignment: .top, spacing: 0) {
                                        SampleView(.purple, count: counts["Hustles"] ?? 20)
                                            .id(TabProfile.hustles)
                                            .containerRelativeFrame(.horizontal)
                                        
                                        SampleView(.red, count: counts["Jobs"] ?? 2)
                                            .id(TabProfile.jobs)
                                            .containerRelativeFrame(.horizontal)
                                        
                                        SampleView(.blue, count: counts["Likes"] ?? 20)
                                            .id(TabProfile.likes)
                                            .containerRelativeFrame(.horizontal)
                                    }
                                    .scrollTargetLayout()
                                    .offsetX { value in
                                        let progress = -value / (size.width * CGFloat(TabProfile.allCases.count - 1))
                                        tabProgress = max(min(progress, 1), 0)
                                    }
                                }
                                .scrollPosition(id: $selectedTab)
                                .scrollIndicators(.hidden)
                                .scrollTargetBehavior(.viewAligned)
                                .scrollClipDisabled()
                            }
                        }
                    }
                    .scrollIndicators(.hidden)
                    .onChange(of: selectedTab) { oldValue, newValue in
                        let old = counts[oldValue?.rawValue ?? ""] ?? 0
                        let new = counts[newValue?.rawValue ?? ""] ?? 0
                        
                        if old > new {
                            withAnimation { proxy.scrollTo("top", anchor: .bottom) }
                        }
                    }
                })
            }
        }
        .ignoresSafeArea()
    }
    func buttonTab() -> some View {
        HStack(spacing: 0) {
            ForEach(TabProfile.allCases, id: \.rawValue) { tab in
                HStack(spacing: 10) {
                    Text(tab.rawValue)
                        .font(.callout)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .contentShape(.capsule)
                .onTapGesture {
                    UIImpactFeedbackGenerator(style: .light).impactOccurred()
                    withAnimation(.snappy) {
                        selectedTab = tab
                    }
                }
            }
        }
        .tabMask(tabProgress, tabCount: TabProfile.allCases.count)
        .padding(.horizontal, 8)
        .background {
            GeometryReader {
                let size = $0.size
                let capusleWidth = size.width / CGFloat(TabProfile.allCases.count)
                ZStack(alignment: .leading){
                    RoundedRectangle(cornerRadius: 0)
                        .fill(.gray)
                        .frame(height: 1)
                        .offset(y: 40)
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.blue)
                        .frame(width: capusleWidth, height: 2)
                        .offset(x: tabProgress * (size.width - capusleWidth), y: 40)
                }
            }
        }
        .background(colorScheme == .dark ? .black : .white)
    }
    @ViewBuilder
    func SampleView(_ color: Color, count: Int) -> some View {
        LazyVGrid(columns: Array(repeating: GridItem(), count: 2), content: {
            ForEach(1...count, id: \.self) { _ in
                RoundedRectangle(cornerRadius: 15)
                    .fill(color.gradient)
                    .frame(height: 150)
            }
        })
        .padding(15)
        .scrollIndicators(.hidden)
        .scrollClipDisabled()
        .mask {
            Rectangle()
                .padding(.bottom, -100)
        }
    }
}

enum TabProfile: String, CaseIterable {
    case hustles = "Hustles"
    case jobs = "Jobs"
    case likes = "Likes"
    case sale = "4Sale"
    case question = "???"
}
© www.soinside.com 2019 - 2024. All rights reserved.