【问题标题】:Prevent padding above Image at top of ScrollView when scrolling beyond limit in Sheet当滚动超出工作表中的限制时,防止在 ScrollView 顶部的图像上方填充
【发布时间】: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)")
            }
        }
    }
}

【问题讨论】:

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

标签: ios swiftui


【解决方案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:

    1. 将Image 包裹在GeometryReader 中,这样就可以获得全局坐标空间中的位置。
    2. 确保将.scaledToFill() 添加到GeometryReader,否则它不会占用ScrollView 中的任何空间。或者,您可以通过为 GeometryReader 提供默认框架来解决此问题
    3. 使用全局坐标空间中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)")
                }
            }
        }
    }
    

    【讨论】:

    • 这几乎行得通!只是图像保持固定,而像您提到的那样关闭工作表,并且图像纵横比丢失。
    猜你喜欢
    • 2022-11-08
    • 1970-01-01
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 2017-07-01
    相关资源
    最近更新 更多