在iPad上的SwiftUI中呈现ActionSheet

时间:2019-07-06 03:09:49

标签: swift ipad swiftui

我已经收到一个ActionSheet,可以在iPhone设备上正常显示。但它在iPad上崩溃。说它需要弹出窗口的位置。有人用过此代码吗?我正在使用iOS 13 beta 3和Xcode 11 beta3。(这使用的是一个表示ActionSheet的版本,该版本在beta 2中不可用)

import SwiftUI

struct ContentView : View {
    @State var showSheet = false

    var body: some View {
        VStack {
            Button(action: {
                self.showSheet.toggle()
            }) {
                Text("Show")
            }
            .presentation($showSheet) { () -> ActionSheet in
                ActionSheet(title: Text("Hello"))

            }
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

5 个答案:

答案 0 :(得分:8)

最后,如iOS 13.4中所测试,至少在beta中,此问题已得到解决。冲突的约束警告仍然存在,但是崩溃没有了。现在,这是呈现操作表的适当方法。

import SwiftUI

struct ContentView : View {
    @State var showSheet = false

    var body: some View {
        VStack {
            Button(action: {
                self.showSheet.toggle()
            }) {
                Text("Show")
            }
            .actionSheet(isPresented: $showSheet, content: { ActionSheet(title: Text("Hello"))
            })
        }
    }
}

struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

答案 1 :(得分:7)

遗憾的是,此错误尚未在iOS 13的最终发行版中得到修复。developer forums中提到了此错误,我已经对此提出了反馈(FB7397761),但对于目前,UIDevice.current.userInterfaceIdiom == .pad时需要使用其他UI来解决它。

记录下来,(无用的)异常消息是:

2019-10-21 11:26:58.205533-0400 LOOksTape[34365:1769883] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController (<UIAlertController: 0x7f826e094a00>) of style UIAlertControllerStyleActionSheet from _TtGC7SwiftUI19UIHostingController… 
The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. 
You must provide location information for this popover through the alert controller's popoverPresentationController. 
You must provide either a sourceView and sourceRect or a barButtonItem.  
If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'

作为一种解决方法,此popSheet函数将在iPad上显示一个弹出框,并在其他任何地方显示一个ActionSheet

public extension View {
    /// Creates an `ActionSheet` on an iPhone or the equivalent `popover` on an iPad, in order to work around `.actionSheet` crashing on iPad (`FB7397761`).
    ///
    /// - Parameters:
    ///     - isPresented: A `Binding` to whether the action sheet should be shown.
    ///     - content: A closure returning the `PopSheet` to present.
    func popSheet(isPresented: Binding<Bool>, arrowEdge: Edge = .bottom, content: @escaping () -> PopSheet) -> some View {
        Group {
            if UIDevice.current.userInterfaceIdiom == .pad {
                popover(isPresented: isPresented, attachmentAnchor: .rect(.bounds), arrowEdge: arrowEdge, content: { content().popover(isPresented: isPresented) })
            } else {
                actionSheet(isPresented: isPresented, content: { content().actionSheet() })
            }
        }
    }
}

/// A `Popover` on iPad and an `ActionSheet` on iPhone.
public struct PopSheet {
    let title: Text
    let message: Text?
    let buttons: [PopSheet.Button]

    /// Creates an action sheet with the provided buttons.
    public init(title: Text, message: Text? = nil, buttons: [PopSheet.Button] = [.cancel()]) {
        self.title = title
        self.message = message
        self.buttons = buttons
    }

    /// Creates an `ActionSheet` for use on an iPhone device
    func actionSheet() -> ActionSheet {
        ActionSheet(title: title, message: message, buttons: buttons.map({ popButton in
            // convert from PopSheet.Button to ActionSheet.Button (i.e., Alert.Button)
            switch popButton.kind {
            case .default: return .default(popButton.label, action: popButton.action)
            case .cancel: return .cancel(popButton.label, action: popButton.action)
            case .destructive: return .destructive(popButton.label, action: popButton.action)
            }
        }))
    }

    /// Creates a `.popover` for use on an iPad device
    func popover(isPresented: Binding<Bool>) -> some View {
        VStack {
            ForEach(Array(buttons.enumerated()), id: \.offset) { (offset, button) in
                Group {
                    SwiftUI.Button(action: {
                        // hide the popover whenever an action is performed
                        isPresented.wrappedValue = false
                        // another bug: if the action shows a sheet or popover, it will fail unless this one has already been dismissed
                        DispatchQueue.main.async {
                            button.action?()
                        }
                    }, label: {
                        button.label.font(.title)
                    })
                    Divider()
                }
            }
        }
    }

    /// A button representing an operation of an action sheet or popover presentation.
    ///
    /// Basically duplicates `ActionSheet.Button` (i.e., `Alert.Button`).
    public struct Button {
        let kind: Kind
        let label: Text
        let action: (() -> Void)?
        enum Kind { case `default`, cancel, destructive }

        /// Creates a `Button` with the default style.
        public static func `default`(_ label: Text, action: (() -> Void)? = {}) -> Self {
            Self(kind: .default, label: label, action: action)
        }

        /// Creates a `Button` that indicates cancellation of some operation.
        public static func cancel(_ label: Text, action: (() -> Void)? = {}) -> Self {
            Self(kind: .cancel, label: label, action: action)
        }

        /// Creates an `Alert.Button` that indicates cancellation of some operation.
        public static func cancel(_ action: (() -> Void)? = {}) -> Self {
            Self(kind: .cancel, label: Text("Cancel"), action: action)
        }

        /// Creates an `Alert.Button` with a style indicating destruction of some data.
        public static func destructive(_ label: Text, action: (() -> Void)? = {}) -> Self {
            Self(kind: .destructive, label: label, action: action)
        }
    }
}

答案 2 :(得分:0)

这是我针对该错误的解决方法-它维护了iPhone设备的“动作表”功能,而只是为iPad创建了“警报”样式的控制器

对于我的情况来说这很简单,可能会帮助其他人

    var preferredStyle: UIAlertController.Style
    if UIDevice.current.userInterfaceIdiom == .pad {
        preferredStyle = .alert
    }
    else{
        preferredStyle = .actionSheet
    }
    let cellMenu = UIAlertController(title: nil, message: "Bought Item?", preferredStyle: preferredStyle)

    //Create actions
    //Add Actions to menu

    self.present(cellMenu, animated: true, completion: nil)

答案 3 :(得分:0)

尝试使用此组件,它将在iPhone上显示一个actionSheet,在iPad和Mac上显示一个Popover。 https://github.com/AndreaMiotto/ActionOver

答案 4 :(得分:-1)

这是Beta的已知错误。只需等待修复。