Present ActionSheet in SwiftUI on iPad

前端 未结 5 1699
梦谈多话
梦谈多话 2021-02-02 09:45

I\'ve gotten an ActionSheet to present just fine on an iPhone device. But it crashes for iPad. Says it needs the location for the popover. Has anyone had luck with this code? I\

相关标签:
5条回答
  • 2021-02-02 10:19

    Finally, as tested in iOS 13.4 this has been resolved, at least in the beta. The conflicting constraints warning persists, but the crash is gone. This is now the appropriate way to present an action sheet.

    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()
        }
    }
    
    0 讨论(0)
  • 2021-02-02 10:20

    Sadly, this bug has not been fixed for the final release of iOS 13. It was mentioned on the developer forums, and I've filed a feedback for it (FB7397761), but for the time being one needs to work around it by using some other UI when UIDevice.current.userInterfaceIdiom == .pad.

    For the record, the (unhelpful) exception message is:

    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.'
    
    

    As a workaround, this popSheet function will display a popover on the iPad and an ActionSheet everywhere else:

    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)
            }
        }
    }
    
    
    0 讨论(0)
  • 2021-02-02 10:20

    It is a known bug of beta. Just wait for a fix.

    0 讨论(0)
  • 2021-02-02 10:25

    Try this component, it will present an actionSheet on iPhone and a Popover on iPad and Mac. https://github.com/AndreaMiotto/ActionOver

    0 讨论(0)
  • 2021-02-02 10:27

    Here's my workaround for the bug - it maintains the "actionsheet" functionality for iPhone devices but simply creates an "alert" style controller for iPad

    It's simple enough for my case and may help others

        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)
    
    0 讨论(0)
提交回复
热议问题