Swiftui 如何在顶部滚动视图的情况下使背景中的按钮可点击?

Swiftui 如何在顶部滚动视图的情况下使背景中的按钮可点击?,swiftui,Swiftui,我有一个滚动视图,可以在页眉上滑动,但是我需要在页眉上有一个按钮可点击 这就是它看起来的样子: 我需要“开始”按钮可点击,但由于ScrollView位于顶部,因此无法点击。我试着玩zIndex,但标题上方不再出现滚动 这是示例代码: struct ContentView: View { @State private var isPresented = false @State private var headerHeight: CGFloat = 0 var body

我有一个
滚动视图
,可以在页眉上滑动,但是我需要在页眉上有一个按钮可点击

这就是它看起来的样子:

我需要“开始”按钮可点击,但由于
ScrollView
位于顶部,因此无法点击。我试着玩
zIndex
,但标题上方不再出现滚动

这是示例代码:

struct ContentView: View {
    @State private var isPresented = false
    @State private var headerHeight: CGFloat = 0

    var body: some View {
        ZStack(alignment: .top) {
            VStack {
                Button("Start") {
                    isPresented = true
                }
                .padding()
                .frame(maxWidth: .infinity)
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(16)
            }
            .padding(80)
            .background(Color(.label).ignoresSafeArea())
            .assign(heightTo: $headerHeight)
            ScrollView(showsIndicators: false) {
                Spacer()
                    .frame(height: headerHeight)
                VStack {
                    ForEach((0...50), id: \.self) {
                        Text("Some text \($0)")
                    }
                }
                .padding()
                .frame(maxWidth: .infinity)
                .background(Color(.white))
            }
        }
        .navigationBarHidden(true)
        .alert(isPresented: $isPresented) {
            Alert(title: Text("Button tapped"))
        }
    }
}

extension View {
    /// Binds the height of the view to a property.
    func assign(heightTo height: Binding<CGFloat>) -> some View {
        background(
            GeometryReader { geometry in
                Color.clear.onAppear {
                    height.wrappedValue = geometry.size.height
                }
            }
        )
    }
}
struct ContentView:View{
@State private var isPresented=false
@国有私有var headerHeight:CGFloat=0
var body:一些观点{
ZStack(对齐:。顶部){
VStack{
按钮(“开始”){
isPresented=true
}
.padding()
.frame(最大宽度:。无穷大)
.foregroundColor(.白色)
.背景(颜色.蓝色)
.转弯半径(16)
}
.填充(80)
.background(颜色(.label).ignoresSafeArea())
.分配(高度:$headerHeight)
滚动视图(显示指示器:false){
垫片()
.车架(高度:车头高度)
VStack{
ForEach((0…50),id:\.self){
文本(“某些文本\($0)”)
}
}
.padding()
.frame(最大宽度:。无穷大)
.背景(颜色(.白色))
}
}
.navigationBarHidden(真)
.alert(isPresented:$isPresented){
警报(标题:文本(“点击的按钮”))
}
}
}
扩展视图{
///将视图的高度绑定到属性。
func assign(高度到高度:绑定)->某些视图{
背景(
GeometryReader{中的几何体
颜色{
height.wrappedValue=geometry.size.height
}
}
)
}
}
如何在将滚动视图滑动到标题上时实现这一点?

1.0.0版
这不是一件容易的工作!但我让它与下行方式一起工作:

我使用的方法是观看ScrollView的移动,并将数据用于应用程序,当然,它的功能是万能的GeometryReader





受@swiftPunk答案的启发,我使用了一种稍有不同的方法,将标题偏移锁定到滚动位置的初始差异:

struct ContentView3: View {
    @State private var isPresented = false

    // For sticky header
    @State private var scrollOffsetY: CGFloat = 0
    @State private var headerOffsetY: CGFloat = 0
    @State private var headerOffsetDiff: CGFloat = 0
    @State private var headerHeight: CGFloat = 0

    var body: some View {
        ScrollView {
            Color.clear
                .frame(height: headerHeight)
                .overlay(
                    VStack {
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Button(action: { isPresented.toggle() }) {
                            Text("Start")
                                .padding()
                                .frame(maxWidth: .infinity)
                                .foregroundColor(.white)
                                .background(Color.blue)
                                .cornerRadius(16)

                        }
                    }
                    .foregroundColor(.red)
                    .padding()
                    .background(
                        GeometryReader { geometry in
                            Color.clear
                                .onAppear {
                                    headerHeight = geometry.size.height
                                    headerOffsetY = geometry.frame(in: .global).minY
                                }
                        }
                    )
                    .onChange(of: scrollOffsetY) { offset in
                        guard headerOffsetDiff == 0 else { return }
                        headerOffsetDiff = offset - headerOffsetY
                    }
                    .offset(y: headerOffsetY + headerOffsetDiff - scrollOffsetY)
                )
            VStack {
                ForEach((0...30), id: \.self) {
                    Text("Some text \($0)").padding(.bottom)
                }
            }
            .frame(maxWidth: .infinity)
            .padding()
            .background(
                GeometryReader { geometry in
                    Color(.systemBackground)
                        .onChange(of: geometry.frame(in: .global).minY) {
                            scrollOffsetY = $0
                        }
                }
            )
        }
        .background(
            Color(.label)
                .ignoresSafeArea()
        )
        .alert(isPresented: $isPresented) {
            Alert(title: Text("Button tapped"))
        }
    }
}
版本2.0.0
我决定更新我的答案,因为如果我把它们放在同一个答案中可能会混淆,所以我把它放在这里,以保持代码和答案清晰易读。关于新的更新,它基本上是相同的方式,但一个增强版





为什么不列出一个部分?这似乎不是你想要的点击按钮的最佳方式。视觉效果不一样,我需要显示标题上方的滑动。。几乎就像一个底部的床单滑过它。这是可怕的thx!关于8.0在安全区域插图中添加了什么内容的问题?我试图删除它,但确实需要,但这是什么原因?我们对此无能为力,我对ScrollView进行了尸检,以删除它,但它是从ScrollView本身(从苹果)我们应该有8.0在我们的代码中处于中心!你可以去掉那个8,但它会偏离中心,我不知道recommend@TruMan1:我也在代码中添加了额外的解释。这真的很好,谢谢!A发布了另一个完全受你答案启发的变体。我只是在某些方面有所改进,但不能像你一样很好地处理方向的变化。填充会导致计算中的变化,所以我能够消除这种情况,因为将使用不同的区域,并且无法预测填充。此外,我正在确定GeometryReader的范围,而不是用它包装整个ScrollView(只是我个人的喜好,不是对的或错的)。最后一件事是,我通过计算偏移量而不需要知道高度,从而将人头高度和偏移量解耦。我可以看到您正在尝试达到的目标,我添加了另一个更新,也满足了这一需要。很快!它不能很好地适应方向的变化,需要找到一个合适的时间将headerOffsetDiff重置为一个合适的值。如果我的答案中的任何部分不符合您的需要,请致电我的plz,我会为您更新它,一点问题也没有,只要告诉我关于我的代码的问题,我就会解决它。:)非常感谢你的帮助!我从你的回答中学到了很多不幸的是,硬编码的16px仍然存在于偏移量中,当在各种屏幕中重复使用时,它不能很好地工作。你知道怎么摆脱这个吗?你不想在ZStack上加填充物吗?您的意思是在项目列表视图上没有填充?在偏移量中,您有
.offset(y:headerHeight+screenGeometry.safeAreaInsets.top+16.0-offsetY))
。你解释说这是因为你在苹果看到的一些autospy。但是当我在其他地方更改填充时,我必须更改这个数字。我可以做到这一点。
.offset(y:headerHeight+screenGeometry.safeareainsetts.top+8.0-offsetY))
但我们至少应该保留8.0!那是你想要的吗?但为什么有数字?如果苹果改变了某些东西,或者使用了不同的填充物,该怎么办。听起来好像不可能,但只是想知道
struct ButtonView: View {

    @Binding var isPresented: Bool

    var body: some View {

        Button(action: { isPresented.toggle() }, label: {
            
            Text("Start")
                .bold()
                .padding()
                .frame(maxWidth: .infinity)
                .background(Color.purple)
                .foregroundColor(.white)
                .cornerRadius(16)
            
        })
        .padding(80)
  
    }
 
}
struct ContentView3: View {
    @State private var isPresented = false

    // For sticky header
    @State private var scrollOffsetY: CGFloat = 0
    @State private var headerOffsetY: CGFloat = 0
    @State private var headerOffsetDiff: CGFloat = 0
    @State private var headerHeight: CGFloat = 0

    var body: some View {
        ScrollView {
            Color.clear
                .frame(height: headerHeight)
                .overlay(
                    VStack {
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Text("Some text 1")
                        Button(action: { isPresented.toggle() }) {
                            Text("Start")
                                .padding()
                                .frame(maxWidth: .infinity)
                                .foregroundColor(.white)
                                .background(Color.blue)
                                .cornerRadius(16)

                        }
                    }
                    .foregroundColor(.red)
                    .padding()
                    .background(
                        GeometryReader { geometry in
                            Color.clear
                                .onAppear {
                                    headerHeight = geometry.size.height
                                    headerOffsetY = geometry.frame(in: .global).minY
                                }
                        }
                    )
                    .onChange(of: scrollOffsetY) { offset in
                        guard headerOffsetDiff == 0 else { return }
                        headerOffsetDiff = offset - headerOffsetY
                    }
                    .offset(y: headerOffsetY + headerOffsetDiff - scrollOffsetY)
                )
            VStack {
                ForEach((0...30), id: \.self) {
                    Text("Some text \($0)").padding(.bottom)
                }
            }
            .frame(maxWidth: .infinity)
            .padding()
            .background(
                GeometryReader { geometry in
                    Color(.systemBackground)
                        .onChange(of: geometry.frame(in: .global).minY) {
                            scrollOffsetY = $0
                        }
                }
            )
        }
        .background(
            Color(.label)
                .ignoresSafeArea()
        )
        .alert(isPresented: $isPresented) {
            Alert(title: Text("Button tapped"))
        }
    }
}
import SwiftUI

struct ContentView: View {
    
    @State private var isPresented: Bool = Bool()
    @State private var offsetY: CGFloat = CGFloat()
    @State private var headerHeight: CGFloat = CGFloat()
    
    var body: some View {
        
        GeometryReader { screenGeometry in
            
            ZStack {
                
                Color.yellow.ignoresSafeArea()
                
                ScrollView(showsIndicators: false) {
                    
                    VStack(spacing: 0.0) {
                        
                        Color.clear                         // Test if we are in Center! >> change Color.clear to Color.blue and uncomment down code for Capsule() to see it!
                            .frame(height: headerHeight)
                            //.overlay(Capsule().frame(height: 2.0))
                            .overlay( HeaderView(isPresented: $isPresented)
                                        .background( GeometryReader { proxy in Color.clear.onAppear { headerHeight = proxy.size.height } } )
                                        .offset(y: headerHeight + screenGeometry.safeAreaInsets.top - offsetY))
                        
                        ZStack {
                            
                            Color.white.cornerRadius(20.0)
                            
                            VStack { ForEach((0...30), id: \.self) { item in Text("item " + item.description); Divider().padding(.horizontal) } }.padding(.top)
                            
                        }
                        .padding(.horizontal)
                        .overlay( GeometryReader { proxy in Color.clear.onChange(of: proxy.frame(in: .global).minY) { newValue in offsetY = newValue } } )
                        
                    }
                    
                }
                
            }
            .position(x: screenGeometry.size.width/2, y: screenGeometry.size.height/2)
            .alert(isPresented: $isPresented) { Alert(title: Text("Button tapped")) }
            .statusBar(hidden: true)
            
        }
        .shadow(radius: 10.0)
        
    }
    
}
struct HeaderView: View {
    
    @Binding var isPresented: Bool
    
    var body: some View {
        
        ZStack {
            
            Color.clear
            
            VStack(spacing: 20.0) {
                
                button(color: Color(UIColor.systemTeal))
                
                Text("Some text 1").bold()
                
                Text("Some text 2").bold()
                
                button(color: Color(UIColor.green))
                
                Text("Some text 3").bold()
                
                Text("Some text 4").bold()
                
                button(color: Color.purple)
                
            }
            
        }
        .padding()
        
    }
    
    func button(color: Color) -> some View {
        
        return Button(action: { isPresented.toggle() }, label: {
            
            Text("Start")
                .bold()
                .padding()
                .shadow(radius: 10.0)
                .frame(maxWidth: .infinity)
                .background(color)
                .foregroundColor(.white)
                .cornerRadius(16)
            
        })
        
    }
    
}