ios - SwiftUI: ScrollView 中的顶部锚定可调整大小的 View

标签 ios swift xcode swiftui

如何将 ScrollView 中调整大小的 View 锚定到顶部引导点,以便它们在增长时向下增长?

上下文:我正在尝试制作一个 View 的 ScrollView ,我可以通过上下拖动底部的 handle 来调整高度。我的问题是,当它调整大小时,它会上下调整大小。我希望顶部保持不动,只调整它向下移动的距离。

我认为问题不在于 ScrollView ,因为如果我用 VStack 替换它,行为是相同的。但是,在 ScrollView 的上下文中,向上调整大小会使用户无法向上滚动到足以看到 View 顶部的程度。

完整的示例代码位于屏幕截图下方。 iPad 和 iPhone 模拟器都有这个问题

在下面的屏幕截图中, ScrollView 都滚动到顶部。第一个屏幕截图显示了调整最顶部项目大小之前的开始状态

Start-state before resizing the topmost item

第二个屏幕截图显示了调整最顶部项目的大小后的状态 - 现在最顶部的项目超出了列表,因此我们看不到向上滚动以查看顶部

State after resizing the topmost item - the topmost item now goes outside the list so we cannot see scroll up to see the top

下面是完整代码,可在 Xcode 11.0 上运行,以显示问题

struct ScaleItem: View {

    static let defaultHeight: CGFloat = 240.0

    @State var heightDiff: CGFloat = 0.0
    @State var currentHeight: CGFloat = ScaleItem.defaultHeight

    var resizingButton: some View {
        VStack {
            VStack {
                Spacer(minLength: 15)
                HStack {
                    Spacer()
                    Image(systemName: "arrow.up.and.down.square")
                        .background(Color.white)
                    Spacer()
                }
            }
            Spacer()
                .frame(height: 11)
        }
        .background(Color.clear)
    }

    var body: some View {

        ZStack {
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Text("Sample")
                    Spacer()
                }
                Spacer()
            }
            .background(Color.red)
            .overlay(
                RoundedRectangle(cornerRadius: 5.0)
                    .strokeBorder(Color.black, lineWidth: 1.0)
                    .shadow(radius: 3.0)
            )
            .padding()
            .frame(
                minHeight: self.currentHeight + heightDiff,
                idealHeight: self.currentHeight + heightDiff,
                maxHeight: self.currentHeight + heightDiff,
                alignment: .top
            )

            resizingButton
                .gesture(
                    DragGesture()
                        .onChanged({ gesture in
                            print("Changed")
                            let location = gesture.location
                            let startLocation = gesture.startLocation
                            let deltaY = location.y - startLocation.y
                            self.heightDiff = deltaY
                            print(deltaY)
                        })
                    .onEnded { gesture in
                        print("Ended")
                        let location = gesture.location
                        let startLocation = gesture.startLocation
                        let deltaY = location.y - startLocation.y
                        self.currentHeight = max(ScaleItem.defaultHeight, self.currentHeight + deltaY)
                        self.heightDiff = 0
                        print(deltaY)
                        print(String(describing: gesture))
                    })

        }

    }
}

struct ScaleDemoView: View {

    var body: some View {

        ScrollView {
            ForEach(0..<3) { _ in
                ScaleItem()
            }
        }
    }

}

最佳答案

解决此问题的一种方法是在拖动过程中将 ScrollView 重绘到其原始位置。创建一个观察者对象,当您的 DeltaY 发生变化时,将通知父 View 并相应地更新 View 。

  • 首先,创建一个ObservableObject final class DeltaHeight: ObservableObject 并传递给 subview 。
  • .offset(x: 0, y: 0) 添加到您的 ScrollView

测试代码如下:

import SwiftUI

struct ScaleDemoView_Previews: PreviewProvider {
    static var previews: some View {
        ScaleDemoView()
    }
}

struct ScaleItem: View {
    @ObservedObject var delta: DeltaHeight

    static let defaultHeight: CGFloat = 200.0

    @State var heightDiff: CGFloat = 0.0
    @State var currentHeight: CGFloat = ScaleItem.defaultHeight

    var resizingButton: some View {
        VStack {
            VStack {
                Spacer(minLength: 15)
                HStack {
                    Spacer()
                    Image(systemName: "arrow.up.and.down.square")
                        .background(Color.white)
                    Spacer()
                }
            }
            Spacer()
                .frame(height: 11)
        }
        .background(Color.clear)
    }

    var body: some View {

        ZStack {
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Text("Sample")
                    Spacer()
                }
                Spacer()
            }
            .background(Color.red)
            .overlay(
                RoundedRectangle(cornerRadius: 5.0)
                    .strokeBorder(Color.black, lineWidth: 1.0)
                    .shadow(radius: 3.0)
            )
            .padding()
            .frame(
                minHeight: self.currentHeight + heightDiff,
                idealHeight: self.currentHeight + heightDiff,
                maxHeight: self.currentHeight + heightDiff,
                alignment: .top
            )

            resizingButton
                .gesture(
                    DragGesture()
                        .onChanged({ gesture in
                            print("Changed")
                            let location = gesture.location
                            let startLocation = gesture.startLocation
                            let deltaY = location.y - startLocation.y
                            self.heightDiff = deltaY
                            print("deltaY: ", deltaY)
                            self.delta.delta = deltaY
                        })
                    .onEnded { gesture in
                        print("Ended")
                        let location = gesture.location
                        let startLocation = gesture.startLocation
                        let deltaY = location.y - startLocation.y
                        self.currentHeight = max(ScaleItem.defaultHeight, self.currentHeight + deltaY)
                        self.heightDiff = 0
                        print(deltaY)
                        print(String(describing: gesture))
                    })

        }

    }
}

struct ScaleDemoView: View {
    @ObservedObject var delta = DeltaHeight()
    var body: some View {
        ScrollView(.vertical) {
            ForEach(0..<3) { _ in
                ScaleItem(delta: self.delta)
            }
        }.offset(x: 0, y: 0)
            .background(Color.green)
    }

}

final class DeltaHeight: ObservableObject {
    @Published  var delta: CGFloat = 0.0
}

关于ios - SwiftUI: ScrollView 中的顶部锚定可调整大小的 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58242823/

相关文章:

ios - 如何使用 UIKit 更快地渲染具有效果的图像

ios - 核心数据 : difference between backgroundContext and child context with privateQueueConcurrencyType?

ios - Swift:如何访问某个 tableView 部分中自定义单元格中的标签

ios - 如何在日历选择日期 Xcode 上添加文本

c - 如何在 XCode 4 中构建目标文件?

ios - 在 Swift 3 和 iOS 中管理 Sqlite 数据库文件(从本地和/或 bundle 复制到应用程序)

iphone - 如何为 UILabel 复制?

ios - 如何将 TSPopover 合并到我的应用程序中,以免导致运行时崩溃?

swift - 无法显示视频轨道 - 无法识别的选择器发送到实例错误

Swift 2.0 - 将元组结构传递给函数