ios 如何避免在具有底部安全区的设备上使用DatePicker进行视图的跳跃转换?

jk9hmnmh  于 9个月前  发布在  iOS
关注(0)|答案(1)|浏览(114)

在我的应用程序中,我有一个显示事件列表的屏幕。有一个View从底部向上滑动,在列表上,所以用户可以快速添加额外的事件与DatePicker,同时仍然看到列表。


的数据
我遇到过奇怪的行为:在没有底部安全区的设备上,如iPhone SE或iPhone 7 Plus,包含DatePickerView的向上滑动动画是平滑的。
然而,在有底部安全区的设备上,如iPhone 14或iPhone 12,动画是跳跃的。虽然它是动画向上,它似乎有一个打嗝沿着。
我首先注意到这个问题在真实的设备上是完全可重现的,但在模拟器中似乎也是可见的。
当我删除DatePicker时,动画是平滑的。

如何在具有底部安全区的设备上使此动画平滑?

下面是我的代码:

import SwiftUI

struct ContentView: View {

    @State var panelPresented: Bool = false

    var body: some View {
        NavigationView {
            ZStack {
                List {
                    ForEach(0..<10, id: \.self) { index in
                        Text("Dummy Data \(index)")
                    }
                }
                    .navigationBarTitle("List View")
                    .navigationBarItems(trailing: Button(action: {
                    withAnimation(.easeInOut(duration: 0.3)) {
                        panelPresented.toggle()
                    }
                }) {
                    Image(systemName: "ellipsis.circle")
                        .imageScale(.large)
                })

                if panelPresented {
                    PanelView(isPresented: $panelPresented)
                        .transition(AnyTransition
                        .asymmetric(insertion: .move(edge: .bottom),
                                    removal: .move(edge: .bottom)))
                        .animation(.easeInOut(duration: 0.3))
                        .zIndex(1)
                }
            }
        }
    }
}

struct PanelView: View {
    @Binding var isPresented: Bool

    @GestureState private var dragOffset: CGFloat = 0
    @State private var panelVerticalOffset: CGFloat = 0
    @State private var panelHeight: CGFloat = 0

    private let dismissVelocityThreshold: CGFloat = 100

    @State private var selectedDate: Date = Date()

    var body: some View {
        VStack(alignment: .center) {
            Spacer()
            VStack(spacing: 0) {
                Toolbar(isPresented: $isPresented)

                VStack {
                    Text("This is a panel where the user can quickly add events with a DatePicker.")
                        .padding()

                    Button {
                        //
                    } label: {
                        Text("Button")
                            .padding()
                    }
                        .buttonStyle(BorderedButtonStyle())

                    DatePicker("Pick date",
                               selection: $selectedDate)
                        .datePickerStyle(.wheel)
                        .labelsHidden()
                }
            }
                .background(Color(UIColor.systemBackground))
                .background(Color.white
                .shadow(color: Color.secondary.opacity(0.5),
                        radius: 8,
                        x: 0,
                        y: 0)
            )
        }
            .background(GeometryReader { geometry in
            Color.clear
                .preference(key: PanelHeightPreferenceKey.self, value: geometry.size.height)
        })
            .onPreferenceChange(PanelHeightPreferenceKey.self) { newValue in
            panelHeight = newValue
        }
            .offset(y: isPresented ? (panelVerticalOffset + dragOffset) : panelHeight)
            .animation(.easeInOut, value: panelVerticalOffset)
            .gesture(
            DragGesture(minimumDistance: 10, coordinateSpace: .local)
                .updating($dragOffset) { value, state, _ in
                if value.translation.height >= 0 {
                    state = value.translation.height
                } else {
                    state = 0
                }
            }
                .onEnded { value in
                let predictedTranslationHeight = value.predictedEndLocation.y - value.location.y

                if predictedTranslationHeight > dismissVelocityThreshold {
                    withAnimation {
                        isPresented = false
                    }
                } else {
                    withAnimation(.easeInOut) {
                        panelVerticalOffset = 0
                    }
                }
            }
        )
            .onChange(of: isPresented) { newValue in
            if newValue {
                panelVerticalOffset = 0
            }
        }
    }

    struct Toolbar: View {
        @Binding var isPresented: Bool

        var body: some View {
            ZStack {
                Text("Data Entry Panel")
                    .font(.headline)
                    .padding()

                HStack {
                    Spacer()
                    Button {
                        // Hide the panel
                        isPresented = false
                    } label: {
                        Text("Done")
                            .font(.headline)
                    }
                        .padding(.trailing)
                }
            }
                .frame(maxWidth: .infinity)
                .background(Color(UIColor.secondarySystemBackground))
        }
    }

    struct PanelHeightPreferenceKey: PreferenceKey {
        typealias Value = CGFloat

        static var defaultValue: CGFloat = 0

        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value = nextValue()
        }
    }
}

字符串

**更新:**根据Yrb在评论中的建议,我更新了代码,这样PanelView就不会每次都被创建和销毁,而是使用.offset修饰符而不是条件。

struct ContentView: View {

    @State var panelPresented: Bool = false

    var body: some View {
        NavigationView {
            ZStack {
                List {
                    ForEach(0..<10, id: \.self) { index in
                        Text("Dummy Data \(index)")
                    }
                }
                    .navigationBarTitle("List View")
                    .navigationBarItems(trailing: Button(action: {
                    withAnimation(.easeInOut(duration: 0.3)) {
                        panelPresented.toggle()
                    }
                }) {
                    Image(systemName: "ellipsis.circle")
                        .imageScale(.large)
                })

                PanelView(isPresented: $panelPresented)
                    .transition(AnyTransition
                    .asymmetric(insertion: .move(edge: .bottom),
                                removal: .move(edge: .bottom)))
                    .animation(.easeInOut(duration: 0.3))
                    .zIndex(1)
                    .offset(y: panelPresented ? 0 : UIScreen.main.bounds.height)
            }
        }
    }
}


然而,即使在此更改之后,PanelView的视图生命周期与ContentView相同,当面板内有DatePicker时,面板上下滑动时动画中的跳跃仍然存在

uemypmqf

uemypmqf1#

我注意到使用相同ZStack机制的自定义底部工作表也有同样的问题,我通过向DatePicker添加.compositingGroup()修改器来修复动画过程中的“跳转”。
我不确定技术上的含义,但我没有注意到任何奇怪的事情,选择器功能齐全。

相关问题