首页 > 解决方案 > 如何确保 SwiftUI 视图不会滚动到导航栏之外?

问题描述

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

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

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

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

我在这里附上了一个显示行为的 gif,正如您在 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", currentTab: $currentTab, animation: animation)
                    .frame(maxWidth: .infinity)
                TabButton(title: "Tab #3", currentTab: $currentTab, 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
    }
}

标签: swiftswiftuiscrollviewgeometryreader

解决方案


推荐阅读