Swift UI 可滚动选项卡视图

问题描述 投票: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个回答
0
投票
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()
                                }
                                .id("top")
                            
                            Section(header: buttonTab()) {
                                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.