xcode 复制SwiftUI中钱包应用的滚动行为

pvcm50d1  于 2023-06-24  发布在  Swift
关注(0)|答案(1)|浏览(101)

我试图在SwiftUI中复制钱包应用的滚动行为。具体地说,我希望实现这样的效果,即在卡片视图上一直向上滚动返回到主视图。

为了实现这一点,我实现了以下代码:

struct ContentView: View {
    @State private var isCardVisible = true
    @State private var dragOffset: CGFloat = 0
    
    var body: some View {
        ZStack {
            Color.gray.ignoresSafeArea()
            
            if isCardVisible {
                ScrollView {
                    VStack(spacing: 20) {
                        Text("Card View")
                            .font(.largeTitle)
                            .padding()
                            .background(Color.white)
                            .cornerRadius(8)
                            .offset(y: max(dragOffset, 0))
                            .gesture(
                                DragGesture()
                                    .onChanged { value in
                                        dragOffset = value.translation.height
                                    }
                                    .onEnded { value in
                                        if dragOffset < -200 {
                                            isCardVisible = false
                                        } else {
                                            dragOffset = 0
                                        }
                                    }
                            )
                    }
                    .padding()
                }
                .overlay(
                    GeometryReader { geometry in
                        Color.clear
                            .preference(key: OffsetPreferenceKey.self, value: geometry.frame(in: .global).origin.y)
                    }
                )
                .onPreferenceChange(OffsetPreferenceKey.self) { offset in
                    if offset < -200 {
                        isCardVisible = false
                    }
                }
            }
            
            if !isCardVisible {
                Button("Show Card") {
                    isCardVisible = true
                }
                .padding()
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(8)
            }
        }
    }
}

struct OffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

在我的实现中,我使用ZStack来对视图进行分层。卡视图显示在ScrollView内,可以向上或向下滚动。dragOffset状态变量用于跟踪拖动时卡片视图的垂直平移。
为了处理退出行为,我在卡片视图上使用了一个DragGesture。当用户向上滚动并释放手指时,onEnded闭包会检查dragOffset是否小于-200。如果是,则isCardVisible状态设置为false,卡视图将被隐藏。
此外,我还使用GeometryReader来检测卡片视图的滚动偏移量。OffsetPreferenceKey用于存储和跟踪偏移量值,onPreferenceChange修饰符用于检查偏移量是否小于-200,从而触发退出行为。
尽管我做了很多努力,但滚动行为与钱包应用程序中看到的预期效果不匹配。我已经附上了我已经实现的代码沿着这个问题。
有没有人可以提供指导,如何修改此代码,以准确地复制钱包应用程序的滚动行为,其中向上滚动所有的方式返回到主视图?谢谢你!

emeijp43

emeijp431#

你的问题和代码表明你在这方面做了很多研究。不幸的是,您试图将您发现的所有内容合并到这一视图中。你遇到的第一个问题是你不能同时拥有一个垂直的ScrollView和一个垂直的DragGesture。这不管用第二,DragGesturesPreferenceKeys实际上不能像这样一起工作。在这种情况下,您甚至不需要DragGesture,只需要PreferenceKey。第三,不要在ScrollView中的视图上放置偏移量,否则会改变视图的垂直位置。如果DragGesture偶尔能工作,你的视图就会上下跳动。让ScrollView来处理这个问题。下面注解代码:

struct ScrollDownToHide: View {
    @State private var isCardVisible = true
    @State private var dragOffset: CGFloat = 0
    
    var body: some View {
        ZStack {
            Color.gray.ignoresSafeArea()
            
            if isCardVisible {
                ScrollView {
                    VStack(spacing: 20) {
                        Text("Card View")
                            .font(.largeTitle)
                            .padding()
                            .background(Color.white)
                            .cornerRadius(8)
                            // The GeometryReader needs to be on the card view itself.
                            // You need to know where card view is, not the
                            // ScrollView's location on the screen.
                            .overlay(
                                GeometryReader { geometry in
                                    Color.clear
                                        .preference(key: OffsetPreferenceKey.self, value: geometry.frame(in: .global).origin.y)
                                }
                            )
                            // This could have been anywhere in the view as long as it
                            // was on a visible view.
                            .onPreferenceChange(OffsetPreferenceKey.self) { value in
                                dragOffset = value
                            }
                    }
                    .padding()
                }
                // read the dragOffset in an `.onChange(of:)` to catch the updated values.
                // SwiftUI's origin in this case has (0,0) in the upper left
                // so origin.y will increase as the card goes down the screen.
                .onChange(of: dragOffset) { newValue in
                    if newValue > 200 {
                        withAnimation {
                            isCardVisible.toggle()
                        }
                    }
                }
            } else {
                Button("Show Card") {
                    isCardVisible = true
                }
                .padding()
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(8)
            }
        }
    }
}

// With the PreferenceKey, you do not need the func reduce to do anything to the value,
// you are simply passing the y value along.
struct OffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}

最后,将卡片放回原处的实际动画被称为“英雄动画”,它使用MatchedGeometry效果。谷歌,你会发现很多关于使用它的教程。

相关问题