我正在制作一个简单的视图与滚动视图和顶部标题的内容.当用户向下滚动时,我想隐藏标题,而当用户向上滚动时,我想要显示它.我有三个不同的选项卡,如果我手动在它们之间滑动,一切都很好.如果我try 并单击标题中的按钮来切换选项卡,滚动视图稍微调整了一下滚动视图,但什么也不做,那么我就会收到下面的大量错误.

The code works when (either or)

  1. 移除.ignoresSafeArea(edges: .top)可以解决该问题
  2. 删除TabView并使用条件呈现来显示选项卡.

我想保留忽略安全区域和选项卡视图的功能,我如何解决这个问题?

The behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values

import SwiftUI

struct FeedViewSec: View {
    @Environment(\.colorScheme) var colorScheme
    @State private var selection = 0
    @State var headerHeight: CGFloat = 130
    @State var headerOffset: CGFloat = 0
    @State var lastHeaderOffset: CGFloat = 0
    @State var direction: SwipeDirection = .none
    @State var shiftOffset: CGFloat = 0

    var body: some View {
        NavigationStack {
            ZStack(alignment: .bottomTrailing){
                TabView(selection: $selection) {
                    scrollBody().tag(0)
                    scrollBody().tag(1)
                    scrollBody().tag(2)
                }.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            }
            .overlay(alignment: .top) {
                headerView().offset(y: -headerOffset < headerHeight ? headerOffset : (headerOffset < 0 ? headerOffset : 0))
            }
            .ignoresSafeArea(edges: .top)
        }
    }
    func scrollBody() -> some View {
        ScrollView {
            LazyVStack {
                Color.clear.frame(height: 130)
                ForEach(0..<30){ i in
                    RoundedRectangle(cornerRadius: 15).frame(width: 100, height: 100)
                }
            }
            .offsetY { previous, current in
                if previous > current {
                    if direction != .up && current < 0{
                        shiftOffset = current - headerOffset
                        direction = .up
                        lastHeaderOffset = headerOffset
                    }
                    
                    let offset = current < 0 ? (current - shiftOffset) : 0

                    headerOffset = (-offset < headerHeight ? (offset < 0 ? offset : 0) : -headerHeight * 2.0)
                } else {
                    if direction != .down{
                        shiftOffset = current
                        direction = .down
                        lastHeaderOffset = headerOffset
                    }
                    
                    let offset = lastHeaderOffset + (current - shiftOffset)
                    headerOffset = (offset > 0 ? 0 : offset)
                }
            }
        }.coordinateSpace(name: "SCROLL")
    }
    func headerView() -> some View {
        VStack(spacing: 0){
            HStack {
                HStack(spacing: 1){
                    Text("Explore").font(.title).bold()
                    Image(systemName: "chevron.down").font(.body).bold()
                }
                Spacer()
            }
            .padding(.leading)
            HStack(alignment: .center, spacing: 0) {
                Button {
                    withAnimation(.easeInOut){
                        selection = 0
                    }
                } label: {
                    Text("New")
                        .foregroundColor(.black).bold()
                        .frame(width: 80, height: 25)
                }
                .background((selection == 0) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
                Button {
                    withAnimation(.easeInOut){
                        selection = 1
                    }
                } label: {
                    Text("LeaderBoard")
                        .foregroundColor(.black).bold()
                        .frame(width: 120, height: 25)
                }
                .background((selection == 1) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
                Button {
                    withAnimation(.easeInOut){
                        selection = 2
                    }
                } label: {
                    Text("Hot")
                        .foregroundColor(.black).bold()
                        .frame(width: 80, height: 25)
                }
                .background((selection == 2) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
            }
            .mask {
                RoundedRectangle(cornerRadius: 5)
            }
            .padding(.top, 8)
            Color.clear.frame(height: 13)
        }
        .padding(.top, top_Inset())
        .background(.ultraThinMaterial)
    }
}

func top_Inset() -> CGFloat {
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    
    return window?.safeAreaInsets.top ?? 0
}
extension View{
    @ViewBuilder
    func offsetY(completion: @escaping (CGFloat,CGFloat)->())->some View{
        self.modifier(OffsetHelper(onChange: completion))
    }
    func safeArea()->UIEdgeInsets{
        guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else{return .zero}
        guard let safeArea = scene.windows.first?.safeAreaInsets else{return .zero}
        return safeArea
    }
}
struct OffsetHelper: ViewModifier{
    var onChange: (CGFloat,CGFloat)->()
    @State var currentOffset: CGFloat = 0
    @State var previousOffset: CGFloat = 0
    
    func body(content: Content) -> some View {
        content
            .overlay {
                GeometryReader{proxy in
                    let minY = proxy.frame(in: .named("SCROLL")).minY
                    Color.clear
                        .preference(key: OffsetKeyNew.self, value: minY)
                        .onPreferenceChange(OffsetKeyNew.self) { value in
                            previousOffset = currentOffset
                            currentOffset = value
                            onChange(previousOffset,currentOffset)
                        }
                }
            }
    }
}
struct OffsetKeyNew: PreferenceKey{
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
struct HeaderBoundsKey: PreferenceKey{
    static var defaultValue: Anchor<CGRect>?
    
    static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
        value = nextValue()
    }
}
enum SwipeDirection{
    case up
    case down
    case none
}

推荐答案

以下是KavSoft提供的完整工作解决方案.我建议大家看看他的YouTube,这太棒了.

import SwiftUI

struct FeedViewSec: View {
    @Environment(\.colorScheme) var colorScheme
    @State private var selection = 0
    @State var headerHeight: CGFloat = 130
    @State var headerOffset: CGFloat = 0
    @State var lastHeaderOffset: CGFloat = 0
    @State var direction: SwipeDirection = .none
    @State var shiftOffset: CGFloat = 0

    var body: some View {
        NavigationStack {
            ScrollView(.init()){
                TabView(selection: $selection) {
                    scrollBody().tag(0)
                    scrollBody().tag(1)
                    scrollBody().tag(2)
                }
                .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                .overlay(alignment: .top) {
                    headerView()
                        .offset(y: -headerOffset < headerHeight ? headerOffset : (headerOffset < 0 ? headerOffset : 0))
                }
            }
            .ignoresSafeArea()
        }
    }
    
    @ViewBuilder
    func scrollBody() -> some View {
        ScrollView {
            Rectangle()
                .fill(.clear)
                .frame(height: 90 + top_Inset())
                .offsetY { previous, current in
                    if previous > current {
                        if direction != .up && current < 0{
                            shiftOffset = current - headerOffset
                            direction = .up
                            lastHeaderOffset = headerOffset
                        }
                        
                        let offset = current < 0 ? (current - shiftOffset) : 0

                        headerOffset = (-offset < headerHeight ? (offset < 0 ? offset : 0) : -headerHeight * 2.0)
                    } else {
                        if direction != .down{
                            shiftOffset = current
                            direction = .down
                            lastHeaderOffset = headerOffset
                        }
                        
                        let offset = lastHeaderOffset + (current - shiftOffset)
                        headerOffset = (offset > 0 ? 0 : offset)
                    }
                }
            
            LazyVStack {
                ForEach(0..<30){ i in
                    RoundedRectangle(cornerRadius: 15).frame(width: 100, height: 100)
                }
            }
        }
        .coordinateSpace(name: "SCROLL")
    }
    
    @ViewBuilder
    func headerView() -> some View {
        VStack(spacing: 0){
            HStack {
                HStack(spacing: 1){
                    Text("Explore").font(.title).bold()
                    Image(systemName: "chevron.down").font(.body).bold()
                }
                Spacer()
            }
            .padding(.leading)
            HStack(alignment: .center, spacing: 0) {
                Button {
                    withAnimation(.easeInOut){
                        selection = 0
                    }
                } label: {
                    Text("New")
                        .foregroundColor(.black).bold()
                        .frame(width: 80, height: 25)
                }
                .background((selection == 0) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
                Button {
                    withAnimation(.easeInOut){
                        selection = 1
                    }
                } label: {
                    Text("LeaderBoard")
                        .foregroundColor(.black).bold()
                        .frame(width: 120, height: 25)
                }
                .background((selection == 1) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
                Button {
                    withAnimation(.easeInOut){
                        selection = 2
                    }
                } label: {
                    Text("Hot")
                        .foregroundColor(.black).bold()
                        .frame(width: 80, height: 25)
                }
                .background((selection == 2) ? colorScheme == .dark ? .gray.opacity(0.3) : .gray : colorScheme == .dark ? .gray : .gray.opacity(0.3))
            }
            .mask {
                RoundedRectangle(cornerRadius: 5)
            }
            .padding(.top, 8)
        }
        .frame(height: 90)
        .padding(.top, top_Inset())
        .background(.ultraThinMaterial)
    }
}


extension View{
    @ViewBuilder
    func offsetY(completion: @escaping (CGFloat,CGFloat)->())->some View{
        self.modifier(OffsetHelper(onChange: completion))
    }
    func safeArea()->UIEdgeInsets{
        guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else{return .zero}
        guard let safeArea = scene.windows.first?.safeAreaInsets else{return .zero}
        return safeArea
    }
}
struct OffsetHelper: ViewModifier{
    var onChange: (CGFloat,CGFloat)->()
    @State var currentOffset: CGFloat = 0
    @State var previousOffset: CGFloat = 0
    
    func body(content: Content) -> some View {
        content
            .overlay {
                GeometryReader{proxy in
                    let minY = proxy.frame(in: .named("SCROLL")).minY
                    Color.clear
                        .preference(key: OffsetKeyNew.self, value: minY)
                        .onPreferenceChange(OffsetKeyNew.self) { value in
                            previousOffset = currentOffset
                            currentOffset = value
                            onChange(previousOffset,currentOffset)
                        }
                }
            }
    }
}
struct OffsetKeyNew: PreferenceKey{
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
struct HeaderBoundsKey: PreferenceKey{
    static var defaultValue: Anchor<CGRect>?
    
    static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
        value = nextValue()
    }
}
enum SwipeDirection{
    case up
    case down
    case none
}

Ios相关问答推荐

Flutter WebRTC:GetUserMedia()不适用于Safari和IOS设备

带有动画层的UIView表示不会在工作表中设置动画

如何在SwiftUI中以一定Angular 绘制直线(&A)

如何使用Swift以编程方式更改SVG文件中某些元素的 colored颜色

如何在 Flutter 应用程序中测试来自 App/Play Store 的移动深层链接?

在 Swift 项目中使用情节提要加载 obj-c 文件

NavigationLink 只能在文本部分点击

如何使用 pushViewController 从 UIHostingController 的 SwiftUI 视图中推送 ViewController?

包装在 AnyView 中时 UIViewControllerRepresentable 无法正常工作

在 SwiftUI 中将 TextEditor 文本计数连接到 ProgressView

从父母到子元素的 Swift Pass 事件

从远程通知启动时,iOS 应用程序加载错误

如何将 ImageView 放置在 UIView 的中心?

快速禁用 UITextfield 的用户输入

将图像保存到 Documents 目录并检索邮箱附件

如何在 SwiftUI 中检测 TextField 的实时变化?

iOS - 确保在主线程上执行

标题为 UIImage 的导航栏

两个数字之间的动画UILabel文本?

iOS 中的 Google Analytics SDK 3.0 _sqlite3 链接器错误