web-dev-qa-db-fra.com

WKWebview avec le nouveau plantage modal iOS13 lorsqu'un sélecteur de fichiers est invoqué

J'ai une vue Web dans un contrôleur de vue modale sur iOS13. Lorsque l'utilisateur essaie de télécharger une image sur la vue Web, il se bloque.

C'est l'exception que je reçois:

2019-09-30 17: 50: 10.676940 + 0900 Engage [988: 157733] * Arrêt de l'application en raison d'une exception non interceptée 'NSGenericException', raison: 'Votre application a présenté un UIDocumentMenuViewController (). Dans son environnement de traits actuel, le modalPresentationStyle d'un UIDocumentMenuViewController avec ce style est UIModalPresentationPopover. Vous devez fournir des informations d'emplacement pour ce popover via le popoverPresentationController du contrôleur de vue. Vous devez fournir un sourceView et un sourceRect ou un barButtonItem. Si ces informations ne sont pas connues lorsque vous présentez le contrôleur de vue, vous pouvez les fournir dans la méthode UIPopoverPresentationControllerDelegate -prepareForPopoverPresentation. ' * Première pile d'appel de touche: (0x18926c98c 0x188f950a4 0x18cb898a8 0x18cb939b4 0x18cb914f8 0x18d283b98 0x18d2737c0 0x18d2a3594 0x1891e9c48 0x1891e4b34 0x1891e5100 0x1891e48bc 0x193050328 0x18d27a6d4 0x1002e6de4 0x18906f460) libc ++ abi.dylib: mettre fin à l'exception non interceptée du genre NSException

Je ne sais pas où pourrais-je placer ce délégué ...

J'ai fait un exemple de projet: https://github.com/ntnmrndn/WKUploadFormCrash Et rempli un rapport de bogue à Apple

12
Antzi

Détails

  • Swift 5.1, Xcode 11.2.1

Solution

import UIKit
import WebKit

protocol WebViewTapGestureRecognizable {
    var lastWebViewTapPosition: CGPoint { get }
}

extension WebViewTapGestureRecognizable where Self: UIViewController {
    func prepareForPresent(_ viewControllerToPresent: UIViewController) {
         if viewControllerToPresent is UIDocumentMenuViewController || viewControllerToPresent is UIDocumentPickerViewController,
            UIDevice.current.userInterfaceIdiom == .phone,
            let popoverPresentationController = viewControllerToPresent.popoverPresentationController {
                popoverPresentationController.sourceView = view
                let Origin = (self as WebViewTapGestureRecognizable).lastWebViewTapPosition
                popoverPresentationController.sourceRect = CGRect(Origin: Origin, size: CGSize(width: 1, height: 1))
        }
    }
}

class WebView: WKWebView {
    private(set) var lastWebViewTapPosition: CGPoint = CGPoint(x: 0, y: 0)

    override init(frame: CGRect, configuration: WKWebViewConfiguration) {
        super.init(frame: frame, configuration: configuration)
        setupGestureRecognizer()
    }

    required init?(coder: NSCoder) { super.init(coder: coder) }
}

extension WebView: WebViewTapGestureRecognizable, UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                            shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    @objc func webViewTapped(_ sender: UITapGestureRecognizer) {
        lastWebViewTapPosition = sender.location(in: superview ?? self)
    }

    private func setupGestureRecognizer() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(webViewTapped(_:)) )
        tapGesture.delegate = self
        addGestureRecognizer(tapGesture)
    }
}

Usage

Pour UIViewController

import UIKit

class ViewController: UIViewController {
    private weak var webView: WebView!
}

extension ViewController: WebViewTapGestureRecognizable {
    var lastWebViewTapPosition: CGPoint { return webView.lastWebViewTapPosition }
    override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.prepareForPresent(viewControllerToPresent)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

Pour NavigationController

import UIKit

class NavigationController: UINavigationController {}

extension NavigationController: WebViewTapGestureRecognizable {
    var lastWebViewTapPosition: CGPoint { return (visibleViewController as? WebViewTapGestureRecognizable)?.lastWebViewTapPosition ?? .zero }
    override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.prepareForPresent(viewControllerToPresent)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

Échantillon complet

N'oubliez pas de coller ici le code de la solution

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class RootViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        let stackView = UIStackView()
        stackView.axis = .vertical
        view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

        var button = UIButton()
        button.setTitle("Present VC", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(buttonTouchedUpInside1), for: .touchUpInside)
        stackView.addArrangedSubview(button)

        button = UIButton()
        button.setTitle("Present NavVC", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(buttonTouchedUpInside2), for: .touchUpInside)
        stackView.addArrangedSubview(button)
    }

    @objc func buttonTouchedUpInside1() {
        present(ViewController(), animated: true, completion: nil)
    }

    @objc func buttonTouchedUpInside2() {
        present(NavigationController(rootViewController: ViewController()), animated: true, completion: nil)
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class ViewController: UIViewController {

    private weak var webView: WebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        createWebView()
    }

    private func createWebView() {
        let webView = WebView(frame: .zero,
                              configuration: WKWebViewConfiguration())
        view.addSubview(webView)
        self.webView = webView
        //webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: webView.bottomAnchor).isActive = true
        view.rightAnchor.constraint(equalTo: webView.rightAnchor).isActive = true

        let urlString = "https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file"
        webView.load(URLRequest(url: URL(string: urlString)!))
    }
}

extension ViewController: WebViewTapGestureRecognizable {
    var lastWebViewTapPosition: CGPoint { return webView.lastWebViewTapPosition }
    override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.prepareForPresent(viewControllerToPresent)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


import UIKit

class NavigationController: UINavigationController {}

extension NavigationController: WebViewTapGestureRecognizable {
    var lastWebViewTapPosition: CGPoint { return (visibleViewController as? WebViewTapGestureRecognizable)?.lastWebViewTapPosition ?? .zero }
    override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.prepareForPresent(viewControllerToPresent)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

Résultats

enter image description here

enter image description here

1
Vasily Bodnarchuk

J'ai trouvé la solution ici: https://medium.com/swlh/popover-menu-over-cards-containing-webkit-views-on-ios-13-a16705aff8af . Merci à l'auteur.

Je viens d'ajouter ces deux fonctions pour éviter le plantage.

override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
      if #available(iOS 13, *), viewControllerToPresent is UIDocumentMenuViewController {
        viewControllerToPresent.popoverPresentationController?.delegate = self
      }
      super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
      popoverPresentationController.sourceView = self.view
    }

Ensuite, vous devez définir la position de votre popover.

0
Filoux