SwiftUI-半模态?

时间:2019-06-21 09:37:53

标签: swift xcode swiftui ios13

我正在尝试像Swift13中的iOS13中的Safari一样重新创建Modal:

是这样的:

enter image description here

有人知道在 SwiftUI 中是否可行?我想显示一个小半模态,可以选择拖动到全屏模式,就像共享表一样。

任何建议都非常感谢!

9 个答案:

答案 0 :(得分:5)

我编写了一个SwiftUI软件包,其中包含自定义iOS 13(如半模式和其按钮)。

GitHub存储库:https://github.com/ViktorMaric/HalfModal

Preview of HalfModal

答案 1 :(得分:4)

在 Swift 5.5 iOS 15+ 和 Mac Catalyst 15+ 中有一个

有一个新的解决方案 Generics

https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/3810055-adaptivesheetpresentationcontrol?changes=__4

adaptiveSheetPresentationController

答案 2 :(得分:2)

Beta 2 Beta 3开始,您不能将模式View表示为.fullScreen。它表示为.automatic -> .pageSheet。但是,即使解决了这个问题,我也非常怀疑它们是否会为您免费提供拖动功能。它将包含在文档中。

您现在可以使用this answer来全屏显示。 Gist here

然后,在演示之后,这是一个如何重新创建交互的简单而肮脏的示例。

    @State var drag: CGFloat = 0.0

    var body: some View {
        ZStack(alignment: .bottom) {
            Spacer() // Use the full space
            Color.red
                .frame(maxHeight: 300 + self.drag) // Whatever minimum height you want, plus the drag offset
                .gesture(
                    DragGesture(coordinateSpace: .global) // if you use .local the frame will jump around
                        .onChanged({ (value) in
                            self.drag = max(0, -value.translation.height)
                        })
                )
        }
    }

答案 3 :(得分:2)

我编写了一个Swift包,其中包含一个自定义修饰符,可让您使用半模态工作表。

以下是链接:https://github.com/AndreaMiotto/PartialSheet

随意使用或贡献

enter image description here

答案 4 :(得分:2)

安德烈·卡雷拉(Andre Carrera)的回答很好,可以随意使用他提供的以下指南:https://www.mozzafiller.com/posts/swiftui-slide-over-card-like-maps-stocks

我修改了SlideOverCard结构,以便它使用设备的实际高度来测量该卡应该停在的位置(您可以使用bounds.height进行调整以适应您的需求):

struct SlideOverCard<Content: View>: View {

    var bounds = UIScreen.main.bounds
    @GestureState private var dragState = DragState.inactive
    @State var position = UIScreen.main.bounds.height/2

    var content: () -> Content
    var body: some View {
        let drag = DragGesture()
            .updating($dragState) { drag, state, transaction in
                state = .dragging(translation: drag.translation)
            }
            .onEnded(onDragEnded)

        return Group {
            Handle()
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.white)
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position + self.dragState.translation.height)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }

    private func onDragEnded(drag: DragGesture.Value) {
        let verticalDirection = drag.predictedEndLocation.y - drag.location.y
        let cardTopEdgeLocation = self.position + drag.translation.height
        let positionAbove: CGFloat
        let positionBelow: CGFloat
        let closestPosition: CGFloat

        if cardTopEdgeLocation <= bounds.height/2 {
            positionAbove = bounds.height/7
            positionBelow = bounds.height/2
        } else {
            positionAbove = bounds.height/2
            positionBelow = bounds.height - (bounds.height/9)
        }

        if (cardTopEdgeLocation - positionAbove) < (positionBelow - cardTopEdgeLocation) {
            closestPosition = positionAbove
        } else {
            closestPosition = positionBelow
        }

        if verticalDirection > 0 {
            self.position = positionBelow
        } else if verticalDirection < 0 {
            self.position = positionAbove
        } else {
            self.position = closestPosition
        }
    }
}

enum DragState {
    case inactive
    case dragging(translation: CGSize)

    var translation: CGSize {
        switch self {
        case .inactive:
            return .zero
        case .dragging(let translation):
            return translation
        }
    }

    var isDragging: Bool {
        switch self {
        case .inactive:
            return false
        case .dragging:
            return true
        }
    }
}

答案 5 :(得分:2)

我认为几乎所有使用 SwiftUI 编写任何内容的 iOS 开发人员都必须遇到这个问题。我确实做到了,但我认为这里的大多数答案要么太复杂,要么没有真正提供我想要的。

我在 GitHub 上编写了一个非常简单的部分工作表,以 Swift 包的形式提供 - HalfASheet

它可能没有其他一些解决方案的花里胡哨,但它做了它需要做的事情。另外,编写自己的代码总是有助于理解正在发生的事情。

注意 - 有几件事 - 首先,这是一项正在进行的工作,请随时改进它,等等。其次,我故意不做 .podspec 就好像你是为 SwiftUI 进行开发时,您至少使用的是 iOS 13,而且我认为 Swift 软件包要好得多...

答案 6 :(得分:1)

这是我幼稚的底页,可以根据其内容进行缩放。无需拖动,但如果需要的话,添加起来应该相对容易:)

struct BottomSheet<SheetContent: View>: ViewModifier {
@Binding var isPresented: Bool
let sheetContent: () -> SheetContent

func body(content: Content) -> some View {
    ZStack {
        content

        if isPresented {
            VStack {
                Spacer()

                VStack {
                    HStack {
                        Spacer()
                        Button(action: {
                            withAnimation(.easeInOut) {
                                self.isPresented = false
                            }
                        }) {
                            Text("done")
                                .padding(.top, 5)
                        }
                    }

                    sheetContent()
                }
                .padding()
            }
            .zIndex(.infinity)
            .transition(.move(edge: .bottom))
            .edgesIgnoringSafeArea(.bottom)
        }
    }
}

extension View {
    func customBottomSheet<SheetContent: View>(
        isPresented: Binding<Bool>,
        sheetContent: @escaping () -> SheetContent
    ) -> some View {
        self.modifier(BottomSheet(isPresented: isPresented, sheetContent: sheetContent))
    }
}

并按如下所示使用:

.customBottomSheet(isPresented: $isPickerPresented) {
                DatePicker(
                    "time",
                    selection: self.$time,
                    displayedComponents: .hourAndMinute
                )
                .labelsHidden()
        }

答案 7 :(得分:0)

您可以自己制作并将其放置在zstack中: https://www.mozzafiller.com/posts/swiftui-slide-over-card-like-maps-stocks

struct SlideOverCard<Content: View> : View {
    @GestureState private var dragState = DragState.inactive
    @State var position = CardPosition.top

    var content: () -> Content
    var body: some View {
        let drag = DragGesture()
            .updating($dragState) { drag, state, transaction in
                state = .dragging(translation: drag.translation)
            }
            .onEnded(onDragEnded)

        return Group {
            Handle()
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.white)
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.rawValue + self.dragState.translation.height)
        .animation(self.dragState.isDragging ? nil : .spring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }

    private func onDragEnded(drag: DragGesture.Value) {
        let verticalDirection = drag.predictedEndLocation.y - drag.location.y
        let cardTopEdgeLocation = self.position.rawValue + drag.translation.height
        let positionAbove: CardPosition
        let positionBelow: CardPosition
        let closestPosition: CardPosition

        if cardTopEdgeLocation <= CardPosition.middle.rawValue {
            positionAbove = .top
            positionBelow = .middle
        } else {
            positionAbove = .middle
            positionBelow = .bottom
        }

        if (cardTopEdgeLocation - positionAbove.rawValue) < (positionBelow.rawValue - cardTopEdgeLocation) {
            closestPosition = positionAbove
        } else {
            closestPosition = positionBelow
        }

        if verticalDirection > 0 {
            self.position = positionBelow
        } else if verticalDirection < 0 {
            self.position = positionAbove
        } else {
            self.position = closestPosition
        }
    }
}

enum CardPosition: CGFloat {
    case top = 100
    case middle = 500
    case bottom = 850
}

enum DragState {
    case inactive
    case dragging(translation: CGSize)

    var translation: CGSize {
        switch self {
        case .inactive:
            return .zero
        case .dragging(let translation):
            return translation
        }
    }

    var isDragging: Bool {
        switch self {
        case .inactive:
            return false
        case .dragging:
            return true
        }
    }
}

答案 8 :(得分:0)

我试图做同样的事情,在 SwiftUI 中以本机方式显示共享表,而不必实现/导入组件。 我在 https://jeevatamil.medium.com/how-to-create-share-sheet-uiactivityviewcontroller-in-swiftui-cef64b26f073

中找到了这个解决方案
struct ShareSheetView: View {
    var body: some View {
        Button(action: actionSheet) {
            Image(systemName: "square.and.arrow.up")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 36, height: 36)
        }
    }
    
    func actionSheet() {
        guard let data = URL(string: "https://www.zoho.com") else { return }
        let av = UIActivityViewController(activityItems: [data], applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
    }
}