0

I know that the normal behavior on iPad is to show a popover view while on iPhone it switches to a full screen, but I don't want the full screen on the smaller devices. Is there a way to prevent this? In UIKit we could override the adaptivepresentationstyle func like this(from https://stackoverflow.com/a/50428131/412154)

 class ViewController: UIViewController {
    @IBAction func doButton(_ sender: Any) {
        let vc = MyPopoverViewController()
        vc.preferredContentSize = CGSize(400,500)
        vc.modalPresentationStyle = .popover
        if let pres = vc.presentationController {
            pres.delegate = self
        }
        self.present(vc, animated: true)
        if let pop = vc.popoverPresentationController {
            pop.sourceView = (sender as! UIView)
            pop.sourceRect = (sender as! UIView).bounds
        }
    }
}
extension ViewController : UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        return .none
    }
}

wondering if anyone found something similar to override for swiftui

thanks for the help!

Prasanth
  • 646
  • 6
  • 21
  • In SwiftUI there mostly nothing to override - it is struct-based, so just wrap your `ViewController` with `adaptivePresentationStyle` into `UIViewControllerRepresentative` – Asperi May 25 '20 at 12:29
  • Ok I was hoping somehow it would obey the horizontal and vertical size class setting. – Prasanth May 25 '20 at 18:50
  • I've seen a lot of people mention how to use uiviewcontrollerrepresentative. I have some of the code working but wondering how to pass in the correct information for button location to anchor the view and show the pointing arrow at the calling button. Does anyone have a working example of this? – Prasanth May 27 '20 at 04:48

3 Answers3

1

From the docs:

presentationCompactAdaptation(horizontal:vertical:)

Some presentations adapt their appearance depending on the context. For example, a popover presentation over a horizontally-compact view uses a sheet appearance by default. Use this modifier to indicate a custom adaptation preference.

    @State private var showInfo = false

    var body: some View {
        Button("View Info") {
            showInfo = true
        }
        .popover(isPresented: $showInfo) {
            InfoView()
                .presentationCompactAdaptation(
                    horizontal: .popover,
                    vertical: .sheet)
        }
    } } ```
malhal
  • 26,330
  • 7
  • 115
  • 133
0

Here is another solution that subclasses uihostingcontroller but I don’t know how I would incorporate it into my swiftui views since I’m trying to achieve this for some deeper views

sampleproject

Heres and alternative that worked better for me

resizable popover

Prasanth
  • 646
  • 6
  • 21
0

This is an improved version of @kou-ariga's code. It also fixed some issues, like:

  • Sometimes popover not showing (when show-hide multiple popups one by one).
  • In some cases, a sheet is opened instead of the popover, and the app crashes.
  • After 1st time, the popup shows the wrong height.

Usage:

@State private var showInfo: Bool = false

// ...

Button {
    showInfo = true
} label: {
    Image(systemName: "info")
}
.alwaysPopover(isPresented: $showInfo) {
    Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam")
        .font(.subheadline)
        .multilineTextAlignment(.center)
        .padding()
        .frame(width: UIDevice.current.userInterfaceIdiom == .phone ? (UIScreen.screenWidth - 16 * 2) : 350)
        .foregroundColor(Color.white)
        .background(Color(.systemGray))
}

Component:

// MARK: - Extension

extension View {
    func alwaysPopover<Content>(
        isPresented: Binding<Bool>,
        permittedArrowDirections: UIPopoverArrowDirection = [.up],
        onDismiss: (() -> Void)? = nil,
        content: @escaping () -> Content
    ) -> some View where Content: View {
        self.modifier(AlwaysPopoverViewModifier(
            isPresented: isPresented,
            permittedArrowDirections: permittedArrowDirections,
            onDismiss: onDismiss,
            content: content
        ))
    }
}

// MARK: - Modifier

struct AlwaysPopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
    @Binding var isPresented: Bool
    let permittedArrowDirections: UIPopoverArrowDirection
    let onDismiss: (() -> Void)?
    let content: () -> PopoverContent

    init(
        isPresented: Binding<Bool>,
        permittedArrowDirections: UIPopoverArrowDirection,
        onDismiss: (() -> Void)? = nil,
        content: @escaping () -> PopoverContent
    ) {
        self._isPresented = isPresented
        self.permittedArrowDirections = permittedArrowDirections
        self.onDismiss = onDismiss
        self.content = content
    }

    func body(content: Content) -> some View {
        content
            .background(
                AlwaysPopover(
                    isPresented: self.$isPresented,
                    permittedArrowDirections: self.permittedArrowDirections,
                    onDismiss: self.onDismiss,
                    content: self.content
                )
            )
    }
}

// MARK: - UIViewController

struct AlwaysPopover<Content: View>: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    let permittedArrowDirections: UIPopoverArrowDirection
    let onDismiss: (() -> Void)?
    @ViewBuilder let content: () -> Content

    init(
        isPresented: Binding<Bool>,
        permittedArrowDirections: UIPopoverArrowDirection,
        onDismiss: (() -> Void)?,
        content: @escaping () -> Content
    ) {
        self._isPresented = isPresented
        self.permittedArrowDirections = permittedArrowDirections
        self.onDismiss = onDismiss
        self.content = content
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self, content: self.content())
    }

    func makeUIViewController(context: Context) -> UIViewController {
        return UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        context.coordinator.host.rootView = self.content()

        guard context.coordinator.lastIsPresentedValue != self.isPresented else { return }

        context.coordinator.lastIsPresentedValue = self.isPresented

        if self.isPresented {
            let host = context.coordinator.host

            if context.coordinator.viewSize == .zero {
                context.coordinator.viewSize = host.sizeThatFits(in: UIView.layoutFittingExpandedSize)
            }

            host.preferredContentSize = context.coordinator.viewSize
            host.modalPresentationStyle = .popover

            host.popoverPresentationController?.delegate = context.coordinator
            host.popoverPresentationController?.sourceView = uiViewController.view
            host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
            host.popoverPresentationController?.permittedArrowDirections = self.permittedArrowDirections

            if let presentedVC = uiViewController.presentedViewController {
                presentedVC.dismiss(animated: true) {
                    uiViewController.present(host, animated: true, completion: nil)
                }
            } else {
                uiViewController.present(host, animated: true, completion: nil)
            }
        }
    }

    class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
        let host: UIHostingController<Content>
        private let parent: AlwaysPopover

        var lastIsPresentedValue: Bool = false

        /// Content view size.
        var viewSize: CGSize = .zero

        init(parent: AlwaysPopover, content: Content) {
            self.parent = parent
            self.host = AlwaysPopoverUIHostingController(
                rootView: content,
                isPresented: self.parent.$isPresented,
                onDismiss: self.parent.onDismiss
            )
        }

        func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
            self.parent.isPresented = false

            if let onDismiss = self.parent.onDismiss {
                onDismiss()
            }
        }

        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .none
        }
    }
}

// MARK: - UIHostingController

class AlwaysPopoverUIHostingController<Content: View>: UIHostingController<Content> {
    @Binding private var isPresented: Bool
    private let onDismiss: (() -> Void)?

    init(rootView: Content, isPresented: Binding<Bool>, onDismiss: (() -> Void)?) {
        self._isPresented = isPresented
        self.onDismiss = onDismiss
        super.init(rootView: rootView)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidDisappear(_ animated: Bool) {
        self.isPresented = false

        if let onDismiss = self.onDismiss {
            onDismiss()
        }
    }
}

Mahmudul Hasan Shohag
  • 2,203
  • 1
  • 22
  • 30