web-dev-qa-db-fra.com

UISplitViewController ne s'effondrera pas correctement au lancement sur iPad iOS 13

Je fais la transition de mon application vers iOS 13 et l'UISplitViewController s'effondre sur la vue détaillée, plutôt que sur le maître au lancement - uniquement sur iPad. En outre, le bouton de retour n'est pas affiché - comme s'il s'agissait du contrôleur de vue racine.

Mon application se compose d'un UISplitViewController qui a été sous-classé, conforme à UISplitViewControllerDelegate. La vue divisée contient deux enfants - les deux UINavigationControllers et est intégrée dans un UITabBarController (sous-classe TabViewController)

Dans la vue fractionnée viewDidLoad, le délégué est défini sur self et preferredDisplayMode est défini sur .allVisible.

Pour une raison quelconque, la méthode splitViewController(_:collapseSecondary:onto:) n'est pas appelée.

Dans iOS 12 sur iPhone et iPad, la méthode splitViewController(_:collapseSecondary:onto:) est correctement appelée au lancement, entre application(didFinishLaunchingWithOptions) et applicationDidBecomeActive.

Dans iOS 1 sur iPhone, la méthode splitViewController(_:collapseSecondary:onto:) est correctement appelée au lancement, entre scene(willConnectTo session:) et sceneWillEnterForeground.

Dans iOS 1 sur iPad, cependant, si la fenêtre a une largeur compacte au lancement, par exemple nouvelle scène créée en vue fractionnée, la méthode splitViewController(_:collapseSecondary:onto:) n'est pas appelée du tout. Ce n'est que lorsque la fenêtre est agrandie à une largeur régulière, puis rétrécie, que la méthode est appelée.

class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        preferredDisplayMode = .allVisible
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        print("Split view controller function")
        guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
        guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
        if topAsDetailController.passedEntry == nil {
            return true
        }
        return false
    }
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        // Setup split controller
        let tabViewController = self.window!.rootViewController as! TabViewController
        let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
        let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
        navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
        navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

        splitViewController.preferredDisplayMode = .allVisible

}
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        if #available(iOS 13.0, *) {
        } else {
            let tabViewController = self.window!.rootViewController as! TabViewController
            let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
            let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
            navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
            navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

            splitViewController.preferredDisplayMode = .allVisible
        }

        return true
    }

Je me demande pourquoi la méthode est appelée sur iPhone, mais pas sur iPad! Je suis un nouveau développeur et ceci est mon premier article, donc excuses si mon code ne donne pas assez de détails ou n'est pas correctement formaté!

15
Belacqua2000

Pour une raison quelconque sur iOS 13 spécifiquement sur l'iPad dans compact traitCollections, l'appel au délégué pour voir s'il doit s'effondrer se produit AVANT que viewDidLoad ne soit appelé sur UISplitViewController et donc lorsqu'il fait cet appel, votre délégué n'est pas défini et la méthode ne se fait jamais appeler.

Si vous créez votre splitViewController par programme, c'est une solution facile, mais si vous utilisez moins les storyboards. Vous pouvez contourner cela en définissant votre délégué dans awakeFromNib () au lieu de viewDidLoad ()

En utilisant votre exemple de la publication d'origine, un exemple de code serait le suivant

class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
        preferredDisplayMode = .allVisible
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        return true
    }
}

Vous voudrez également vous assurer que la logique que vous utilisez dans la fonction collapseSecondary ne fait pas référence à des variables qui ne sont pas encore remplies car viewDidLoad n'a pas encore été appelé.

5
user2898617

J'ai un projet Xcode - maintenant pour iOS 13 - qui utilise un contrôleur de barre d'onglets avec des relations avec cinq contrôleurs de vue fractionnés, chacun avec ses propres vues et contrôleurs de détail (tableau) principaux.

Précédemment - iOS 12.x et versions antérieures, en fait à l'époque où j'écrivais Objective-C - mon délégué de contrôleur de vue divisé était défini dans le code du contrôleur de vue principal de chaque contrôleur de vue divisé (parent) - J'ai placé le délégué dans le sous-classifié La méthode UITableViewController de viewDidLoad. Cela a fonctionné avec succès pendant des années sur iPhone et iPad.

par exemple.

class MasterViewController: UITableViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
        splitViewController?.delegate = self
        ...
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }
}

Pour être clair, je n'ai pas sous-classé le contrôleur de barre d'onglets ou les contrôleurs de vue fractionnée.

Avec la sortie de Xcode 11 et iOS 13, les méthodes de délégué de contrôleur de vue fractionnée dans les contrôleurs de vue maître n'étaient plus appelées.

Pour être clair, pour iOS 13, quel que soit l'appareil ou le simulateur, splitViewController(_:collapseSecondary:onto:) n'est pas appelée (testée à l'aide de points d'arrêt), avec le comportement résultant:

  • iPhone - le contrôleur de vue détaillée est présenté lorsque l'application est exécutée sur l'appareil ou le simulateur.
  • iPad - le contrôleur de vue de détail est présenté lorsque l'application est exécutée sur l'appareil ou le simulateur, sans bouton de retour, il n'y a donc pas de mécanisme évident pour "échapper" à la vue de détail. La seule solution de contournement que j'ai trouvée pour résoudre ce problème consiste à modifier l'orientation du périphérique. Par la suite, le contrôleur de vue fractionnée se comporte comme prévu.

J'ai pensé que cela pouvait avoir quelque chose à voir avec la nouvelle classe SceneDelegate.

J'ai donc ajouté une classe SceneDelegate personnalisée à mes projets de test, puis à mon projet principal.

J'ai la classe SceneDelegate personnalisée qui fonctionne parfaitement. Je le sais car j'ai réussi à définir un window?.tintColor Dans la méthode scene(_:willConnectTo:options:).

Cependant, les problèmes avec les délégués du contrôleur de vue fractionnée ont continué.

J'ai enregistré les commentaires sur Apple et voici leur réponse modifiée ...

... le problème est que vous définissez le délégué de UISplitViewController dans un remplacement de viewDidLoad. Il est possible que UISplitViewController décide de se réduire avant que quoi que ce soit ne provoque le chargement de sa vue. Lorsqu'il le fait, il vérifie son délégué, mais comme le délégué est toujours nul puisque vous ne l'avez pas encore défini, votre code ne sera pas appelé.

Étant donné que les vues sont chargées à la demande, le timing de viewDidLoad peut être imprévisible. En général, il est préférable de configurer des choses comme les délégués du contrôleur de vue plus tôt. Le faire dans scene(willConnectTo: session) est susceptible de mieux fonctionner.

Ce conseil m'a beaucoup aidé.

Dans ma classe SceneDelegate personnalisée, j'ai ajouté le code suivant dans la méthode scene(_:willConnectTo:options:) ...

class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let window = window else { return }
        guard let tabBarController = window.rootViewController as? UITabBarController else { return }

        guard let splitViewController = tabBarController.viewControllers?.first as? UISplitViewController else { return }

        splitViewController.delegate = self
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }

    ...

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }

}

Ce code a fonctionné à la fois pour iPhone et iPad, mais peut-être évidemment pour seulement la première combinaison de contrôleurs de vue de détail maître divisée.

J'ai changé le code pour tenter d'obtenir ce succès pour les cinq contrôleurs de vue fractionnée ...

    guard let window = window else { return }
    guard let tabBarController = window.rootViewController as? UITabBarController else { return }

    guard let splitViewControllers = tabBarController.viewControllers else { return }

    for controller in splitViewControllers {

        guard let splitViewController = controller as? UISplitViewController else { return }
        splitViewController.delegate = self
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }

Ce code fonctionne aussi ... presque ...

Ma vérification pour savoir si return true Pour collapseSecondary est basée sur une valeur unique - une propriété calculée - de chacun des cinq contrôleurs de vue de détail. En raison de cette vérification unique, il m'a semblé difficile de le déterminer dans ma classe SceneDelegate personnalisée, donc dans ma classe SceneDelegate personnalisée, j'ai plutôt écrit le code suivant ...

    guard let window = window else { return }
    guard let tabBarController = window.rootViewController as? UITabBarController else { return }

    guard let splitViewControllers = tabBarController.viewControllers else { return }

    for controller in splitViewControllers {

        guard let splitViewController = controller as? UISplitViewController else { return }
        guard let navigationController = splitViewController.viewControllers.first else { return }
        guard let masterViewController = navigationController.children.first else { return }
        splitViewController.delegate = masterViewController as? UISplitViewControllerDelegate
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }

... puis a rendu chaque contrôleur de vue de détail conforme à UISplitViewControllerDelegate.

par exemple.

class MasterViewController: UITableViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // the following two calls now in the scene(_:willConnectTo:options:) method...
        // splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
        // splitViewController?.delegate = self
        ...
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }
}

Jusqu'à présent, tout va bien, chacun des cinq contrôleurs de vue fractionnée réduit la vue détaillée au démarrage de l'application, pour iPhone et iPad.

1
andrewbuilder

Vous devez ajouter ceci dans la fonction "scène" de la classe "SceneDelegate":

splitViewController.delegate = self

par exemple:

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    // Setup split controller
    let tabViewController = self.window!.rootViewController as! TabViewController
    let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
    let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
    navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
    navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

    splitViewController.preferredDisplayMode = .allVisible

    splitViewController.delegate = self//<<<<<<<<add this

    }
0
Richter