如何确保 SwiftUI 视图不会滚动超出导航栏?

问题描述

我有一个 SwiftUI 视图,其中包含一个可伸缩的标题、一些选项卡和一个滚动视图。

向上滚动时,无论标题内容大小如何,我都希望标签始终停在导航栏上。标题应与标签一致向上滚动。

我已经能够使用固定值来实现这一点,但这不适用于不同的屏幕尺寸等。

无论屏幕大小如何,我该如何做到这一点?

我附上了一个显示行为的 gif here,正如您在 iPhone 8 上看到的那样,这与导航栏不对齐。

import SwiftUI

struct ContentView: View {
    
    @State private var safeArea: EdgeInsets = EdgeInsets(.zero)
    @State private var offset: CGFloat = 0
    @State private var tabBarOffset: CGFloat = 0
    
    @State var currentTab = "Tab #1"
    @Namespace var animation
    
    var body: some View {
        ScrollView {
            vstack(spacing: 0) {
                makeHeader()
                vstack(spacing: 0) {
                    makeTabs()
                    makeBody()
                }
            }
        }
    }
    
    @viewbuilder func makeHeader() -> some View {
        GeometryReader { proxy -> AnyView in
            // Header Observer
            dispatchQueue.main.async {
                offset = proxy.frame(in: .global).minY
                safeArea = proxy.safeAreaInsets
            }
            
            return AnyView(
                ZStack {
                    // Cover
                    Color.gray
                        .frame(maxWidth: .infinity)
                        .frame(height: offset > 0 ? 180 + offset : 180)
                    
                    vstack {
                        Color.white
                            .clipShape(Circle())
                            .frame(width: 60,height: 60)

                        Text("Some Name")
                            .font(.system(size: 24,weight: .medium,design: .rounded))

                        HStack {
                            Text("Some Text #1")
                                .font(.caption)
                            Circle()
                                .frame(width: 3,height: 3)

                            Text("Some Text #2")
                                .font(.caption)
                        }

                    }
                }
                .clipped()
                // Stretchy Effect
                .frame(height: offset > 0 ? 180 + offset : nil)
                .offset(y: offset > 0 ? -offset : -offset < 90 ? 0 : -offset - 90)
            )
        }
        .frame(height: 180)
        .zIndex(1)
    }
    
    @viewbuilder func makeTabs() -> some View {
        vstack(spacing: 0) {
            HStack(spacing: 0) {
                TabButton(title: "Tab #1",currentTab: $currentTab,animation: animation)
                    .frame(maxWidth: .infinity)
                TabButton(title: "Tab #2",animation: animation)
                    .frame(maxWidth: .infinity)
                TabButton(title: "Tab #3",animation: animation)
                    .frame(maxWidth: .infinity)
            }
            Text("tabBarOffset: \(tabBarOffset) offset: \(offset)")
                .font(.caption)
            Divider()
        }
        .padding(.top,20)
        .background(Color.white)
        .offset(y: tabBarOffset < 90 ? -tabBarOffset + 90 : 0)
        .overlay(
            GeometryReader { proxy -> Color in
                dispatchQueue.main.async {
                    tabBarOffset = proxy.frame(in: .global).minY
                }
                return Color.clear
            }
            .frame(width: 0,height: 0),alignment: .top
        )
        .zIndex(1)
    }
    
    @viewbuilder func makeBody() -> some View {
        vstack {
            ForEach((0..<50)) {
                Text("Row #\($0)")
                Divider()
            }
        }
        .padding(.top)
        .zIndex(0)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct TabButton: View {
    var title: String
    @Binding var currentTab: String
    var animation: Namespace.ID
    
    var body: some View {
        Button(
            action: {
                withAnimation { currentTab = title }
            },label: {
                Lazyvstack(spacing: 12) {
                    Text(title)
                        .fontWeight(.semibold)
                        .foregroundColor(currentTab == title ? .blue : .gray)
                        .padding(.horizontal)
                    
                    if currentTab == title {
                        Capsule()
                            .fill(Color.blue)
                            .frame(height: 1.2)
                            .matchedGeometryEffect(id: "TAB",in: animation)
                    } else {
                        Capsule()
                            .fill(Color.clear)
                            .frame(height: 1.2)
                    }
                    
                }
            }
        )
    }
}

class SceneDelegate: UIResponder,UIWindowSceneDelegate {

    var window: UIWindow?
    func scene(_ scene: UIScene,willConnectTo session: UIScenesession,options connectionoptions: UIScene.Connectionoptions) {
        let contentView = ContentView()
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)

            window.rootViewController = UINavigationController(rootViewController: ViewController(rootView: contentView))
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

class ViewController<Content: View>: UIHostingController<Content> {
    override public func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.navigationBar.setBackgroundImage(UIImage(),for: .default)
        navigationController?.navigationBar.barTintColor = .clear
        navigationController?.navigationBar.tintColor = .white
        navigationController?.navigationBar.backgroundColor = .clear
        navigationController?.navigationBar.barStyle = .black
    }

    override public func viewWilldisappear(_ animated: Bool) {
        super.viewWilldisappear(animated)
        navigationController?.navigationBar.setBackgroundImage(nil,for: .default)
        navigationController?.navigationBar.barTintColor = UINavigationBar.appearance().barTintColor
        navigationController?.navigationBar.backgroundColor = UINavigationBar.appearance().backgroundColor
        navigationController?.navigationBar.barStyle = .default
        navigationController?.navigationBar.tintColor = UINavigationBar.appearance().tintColor
    }
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)