Перетаскивание не обновляется

#swiftui

Вопрос:

Я создал представление нижнего листа, которое должно исчезнуть, когда пользователь перетаскивает представление вниз, но DragGesture по какой-то причине не запускается. Кто-нибудь знает, в чем здесь проблема?

 struct BottomSheetView<Content: View>: View {
    @Binding var isOpen: Bool

    let maxHeight: CGFloat
    let minHeight: CGFloat
    let content: Content

    @GestureState private var translation: CGFloat = 0

    private var offset: CGFloat {
        isOpen ? 0 : maxHeight - minHeight
    }

    private var indicator: some View {
        RoundedRectangle(cornerRadius: SheetConstants.radius)
            .fill(Colors.gray)
            .frame(
                width: SheetConstants.indicatorWidth,
                height: SheetConstants.indicatorHeight
        ).onTapGesture {
            self.isOpen.toggle()
        }
    }

    init(isOpen: Binding<Bool>, maxHeight: CGFloat, @ViewBuilder content: () -> Content) {
        self.minHeight = maxHeight * SheetConstants.minHeightRatio
        self.maxHeight = maxHeight
        self.content = content()
        self._isOpen = isOpen
    }

    var body: some View {
        GeometryReader { geometry in
            VStack(spacing: 0) {
                self.indicator.padding()
                self.content
            }
            .frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
            .background(Color.white)
            .clipShape(RoundedShape(corners: [.topLeft, .topRight], radii: SheetConstants.radius))
            .frame(height: geometry.size.height, alignment: .bottom)
            .offset(y: max(self.offset   self.translation, 0))
            .animation(.interactiveSpring())
            .gesture(
                DragGesture().updating(self.$translation) { value, state, _ in
                    state = value.translation.height
                }.onEnded { value in
                    let snapDistance = self.maxHeight * SheetConstants.snapRatio
                    guard abs(value.translation.height) > snapDistance else {
                        return
                    }
                    self.isOpen = value.translation.height < 0
                }
            )
        }
        .opacity(self.isOpen ? 1 : 0)
    }
}

fileprivate enum SheetConstants {
    static let radius: CGFloat = 16
    static let indicatorHeight: CGFloat = 6
    static let indicatorWidth: CGFloat = 60
    static let snapRatio: CGFloat = 0.25
    static let minHeightRatio: CGFloat = 0
}
 

Комментарии:

1. Отлично работает с Xcode 12. Их только я прокомментировал из- clipShape(RoundedShape за отсутствия в предоставленном снимке.

2. @Asperi спасибо, могу я проверить пример кода? потому что это не работает для меня, даже когда я удаляю строку

3. Разместил в ответе все протестированные модули, удалю, если не поможет.

Ответ №1:

Вот полностью протестированный модуль (Xcode 12b5 / iOS 14)

 struct BottomSheetView<Content: View>: View {
    @Binding var isOpen: Bool

    let maxHeight: CGFloat
    let minHeight: CGFloat
    let content: Content

    @GestureState private var translation: CGFloat = 0

    private var offset: CGFloat {
        isOpen ? 0 : maxHeight - minHeight
    }

    private var indicator: some View {
        RoundedRectangle(cornerRadius: SheetConstants.radius)
            .fill(Color.gray)
            .frame(
                width: SheetConstants.indicatorWidth,
                height: SheetConstants.indicatorHeight
        ).onTapGesture {
            self.isOpen.toggle()
        }
    }

    init(isOpen: Binding<Bool>, maxHeight: CGFloat, @ViewBuilder content: () -> Content) {
        self.minHeight = maxHeight * SheetConstants.minHeightRatio
        self.maxHeight = maxHeight
        self.content = content()
        self._isOpen = isOpen
    }

    var body: some View {
        GeometryReader { geometry in
            VStack(spacing: 0) {
                self.indicator.padding()
                self.content
            }
            .frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
            .background(Color.white)
//            .clipShape(RoundedShape(corners: [.topLeft, .topRight], radii: SheetConstants.radius))
            .frame(height: geometry.size.height, alignment: .bottom)
            .offset(y: max(self.offset   self.translation, 0))
            .animation(.interactiveSpring())
            .gesture(
                DragGesture().updating(self.$translation) { value, state, _ in
                    state = value.translation.height
                }.onEnded { value in
                    let snapDistance = self.maxHeight * SheetConstants.snapRatio
                    guard abs(value.translation.height) > snapDistance else {
                        return
                    }
                    self.isOpen = value.translation.height < 0
                }
            )
        }
        .opacity(self.isOpen ? 1 : 0)
    }
}

fileprivate enum SheetConstants {
    static let radius: CGFloat = 16
    static let indicatorHeight: CGFloat = 6
    static let indicatorWidth: CGFloat = 60
    static let snapRatio: CGFloat = 0.25
    static let minHeightRatio: CGFloat = 0
}

struct TestBottomSheet: View {
    @State private var isOpen = false
    var body: some View {
        VStack {
            Button("Open") { isOpen.toggle() }
            BottomSheetView(isOpen: $isOpen, maxHeight: 400) {
                Text("Hello Sheet!")
            }.background(Color.yellow)
        }
    }
}

struct TestBottomSheet_Previews: PreviewProvider {
    static var previews: some View {
        TestBottomSheet()
    }
}
 

Комментарии:

1. спасибо, он работает только в режиме предварительного просмотра, но не закрывается в симуляторе. Я попробую это на реальном устройстве

2. хорошо, мой код и ваш код отлично работают на реальном устройстве, но не работают в симуляторе