当滚动超出工作表中的限制时,防止在 ScrollView 顶部的图像上方填充

Posted

技术标签:

【中文标题】当滚动超出工作表中的限制时,防止在 ScrollView 顶部的图像上方填充【英文标题】:Prevent padding above Image at top of ScrollView when scrolling beyond limit in Sheet 【发布时间】:2020-08-25 03:10:58 【问题描述】:

如果您在 ScrollView 中展示一个在下方显示带有文本的图像的工作表,当您向下滚动然后向上滑动时,请注意它会滚动超出限制并且图像上方会出现空白空间,直到弹性滚动效果稳定.然后,您可以向下拉以关闭工作表。

我想防止在 ScrollView 中的图像上方添加空间。相反,我希望该填充出现在图像和第一个文本之间。执行此操作的应用是 App Store - 当您在 Today 中点击故事时,请注意顶部的图像在向上滚动超过限制时始终保持固定在顶部,并且在其下方出现弹性反弹效果。

我怀疑可以利用 GeometryReader 来获取全局 CoordinateSpace 中的位置,但我不清楚如何利用它来获得所需的行为。

struct ContentView: View 
    @State private var sheetVisible = false
    
    var body: some View 
        VStack 
            Button(action: 
                sheetVisible = true
            , label: 
                Text("Present Sheet")
            )
        
        .sheet(isPresented: $sheetVisible) 
            DetailsView(image: UIImage(named: "test"))
        
    

struct DetailsView: View 
    var image: UIImage?
    
    var body: some View 
        ScrollView 
            Image(uiImage: image ?? UIImage())
                .resizable()
                .aspectRatio((image?.size.width ?? 1) / (image?.size.height ?? 1), contentMode: .fill)
                .background(Color(.secondarySystemFill))
            
            ForEach(0..<50)  index in
                Text("\(index)")
            
        
    

【问题讨论】:

您不想按照***.com/a/63322713/12299030 中的说明禁用退回吗? 虽然这会解决不良行为,但仍需要支持弹性滚动行为,而不是在顶部突然停止滚动 【参考方案1】:

好的,这是您的情况的可能解决方案。使用 Xcode 12b5 / ios 14 测试。

这个想法是有一个内部容器,它在里面滚动,但是在滚动视图坐标空间中读取它的坐标,补偿它相对于容器的偏移图像位置,容器继续滚动,这样就产生了其他一切的效果,即下面的图像,仍然有弹跳。

struct DemoView: View 
    var image: UIImage?

    @State private var offset = CGFloat.zero

    var body: some View 
        ScrollView 
            VStack  // internal container
                Image(uiImage: image ?? UIImage())
                    .resizable()
                    .aspectRatio((image?.size.width ?? 1) / (image?.size.height ?? 1), contentMode: .fill)
                    .background(Color(.secondarySystemFill))
                    .offset(y: offset < 0 ? offset : 0)      // compansate offset

                ForEach(0..<50)  index in
                    Text("\(index)")
                
            
            .background(GeometryReader 
                // read current position of container inside scroll view
                Color.clear.preference(key: ViewOffsetKey.self,
                    value: -$0.frame(in: .named("scroll")).origin.y)
            )
            .onPreferenceChange(ViewOffsetKey.self) 
                self.offset = $0   // needed offset to shift back image
            
        
        .coordinateSpace(name: "scroll")
    


struct ViewOffsetKey: PreferenceKey 
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) 
        value += nextValue()
    

【讨论】:

【参考方案2】:

这可以使用 GeometryReader:

    Image 包裹在GeometryReader 中,这样就可以获得全局坐标空间中的位置。 确保将.scaledToFill() 添加到GeometryReader,否则它不会占用ScrollView 中的任何空间。或者,您可以通过为 GeometryReader 提供默认框架来解决此问题 使用全局坐标空间中Image的minY设置'向上拖动'时的offset。这样就可以模拟图像处于固定位置。

这种技术存在问题。向下拖动工作表时,图像会粘在它的位置上,这看起来很奇怪。我还没有找到解决方法,但也许这个答案会对你有所帮助。

请参见下面的示例。

struct DetailsView: View 
    func getOffsetY(basedOn geo: GeometryProxy) -> CGFloat 
        // Find Y position
        let minY = geo.frame(in: .global).minY
        
        let emptySpaceAboveSheet: CGFloat = 40
        
        // Don't offset view when scrolling down
        if minY <= emptySpaceAboveSheet 
            return 0
        
        
        // Offset the view when it goes above to simulate it standing still
        return -minY + emptySpaceAboveSheet
    
    
    var body: some View 
        ScrollView 
            GeometryReader  imageGeo in
                Image(systemName: "option")
                    .resizable()
                    .scaledToFill()
                    .background(Color(.secondarySystemFill))
                    .offset(x: 0, y: self.getOffsetY(basedOn: imageGeo))
            
            // Need this to make sure the geometryreader has a size
            .scaledToFill()
            
            
            ForEach(0..<50)  index in
                Text("\(index)")
            
        
    

【讨论】:

这几乎行得通!只是图像保持固定,而像您提到的那样关闭工作表,并且图像纵横比丢失。

以上是关于当滚动超出工作表中的限制时,防止在 ScrollView 顶部的图像上方填充的主要内容,如果未能解决你的问题,请参考以下文章

滚动其子uiscrollview时如何防止滚动uiscrollview?

当鼠标悬停在 Firefox 中的嵌入式 iframe 上时,防止父页面滚动

限制UIScrollView中的可滚动区域

如何使DIV的高度固定,当超出固定高度时,出现自动滚动条

如何防止 java.lang.OutOfMemoryError: GC 开销限制在 for 循环期间超出?

滚动时将新项目添加到列表时出现数组索引超出范围异常