SwiftUI:在滚动视图中顶部固定可调整大小的视图

时间:2019-10-04 20:24:55

标签: ios swift xcode swiftui

如何将滚动视图中的视图调整大小固定在最顶端,以使视图在增长时向下增长?

上下文:我正在尝试制作视图的滚动视图,可以通过上下拖动其底部的手柄来调整高度。我的问题是,当它调整大小时,它的调整大小相等。我希望顶部保持原状,并且只调整向下的距离。

我认为问题不在于滚动视图,因为如果将其替换为VStack,其行为是相同的。但是,在滚动视图的上下文中,它向上调整大小会使用户无法向上滚动足够远的距离以查看视图的顶部。

完整的示例代码如下。 iPad和iPhone模拟器都存在该问题

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

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()
            }
        }
    }

}

1 个答案:

答案 0 :(得分:1)

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

  • 首先,创建一个 ObservableObject final class DeltaHeight: ObservableObject并传递到子视图。
  • .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
}