18
votes

UISplitViewController ne se réduira pas correctement au lancement sur iPad iOS 13

Je suis en train de faire la transition de mon application vers iOS 13 et UISplitViewController se replie sur la vue détaillée, plutôt que sur le maître au lancement - uniquement sur iPad. De plus, 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- UISplitViewControllerDelegate , conformément à UISplitViewControllerDelegate . La vue fractionnée contient deux enfants - les deux UINavigationControllers , et est intégrée dans un UITabBarController (sous- TabViewController )

De l'avis partagé viewDidLoad , le délégué est réglé sur self et preferredDisplayMode est réglé sur .allVisible .

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

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

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

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

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
    }
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 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
    }
}

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


10 commentaires

Veuillez déposer un rapport de bogue auprès d'Apple. iOS 13 a eu des problèmes de vue partagée depuis la première version bêta et Apple ne les a toujours pas résolus. Le temps presse.


@rmaddy pouvez-vous être plus précis? Quels problèmes de vue fractionnée sont nouveaux dans iOS 13?


@matt Voir openradar.me/radar?id=4969975819272192


@rmaddy Merci. J'ai du mal à imaginer pourquoi quelqu'un ferait cela (remplacer les deux contrôleurs de vue d'un contrôleur de vue fractionné existant). Une solution de contournement consiste à créer et configurer un nouveau contrôleur de vue fractionnée et à le remplacer par l'ancien (en tant que contrôleur de vue racine de la fenêtre).


@matt C'est mon plan de secours si Apple ne résout pas le bogue. Il est tellement plus simple d'appeler simplement setViewControllers que de créer et de configurer une toute nouvelle vue fractionnée. Ce qui est vraiment étrange, c'est qu'il a été corrigé dans iOS 13 beta 4, puis à nouveau cassé en beta 5. Je n'ai pas encore essayé 13.1.


Merci, j'apprécie votre partage de l'exemple. J'espère que vous me tiendrez au courant. Au fait, lors de mon test de votre projet github sur la bêta 7, le contrôleur de vue fractionnée n'est PAS revenu comme par magie après une rotation et une rotation arrière.


@matt En ce qui concerne la rotation, il doit s'agir d'une rotation qui permuterait entre la largeur compacte et régulière de sorte que la vue fractionnée passe de l'affichage d'une seule colonne à l'affichage des deux. Pour les iPhones, ce n'est que sur les variétés iPhone X, je crois. Pour les iPad, cela dépend de la largeur de l'application en fonction du multitâche.


Ouais, confirmé sur le simulateur 6s Plus.


Cela semble toujours être un problème dans iOS 13.1 - ou est-ce moi?


Oui, c'est toujours un problème pour moi sur iOS 13.1 sur un iPad.


4 Réponses :


0
votes

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

splitViewController.delegate = soi

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 commentaires

6
votes

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

Si vous créez votre splitViewController par programme, c'est une solution facile, mais si vous utilisez des storyboards pas tellement. Vous pouvez contourner ce problème en définissant votre délégué dans awakeFromNib () au lieu de viewDidLoad ()

En utilisant votre exemple de l'article 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 depuis que viewDidLoad n'a pas encore été appelé.


1 commentaires

Cela ne semble toujours pas fonctionner pour moi. La méthode collapseSecondary n'est jamais appelée même lorsque j'ai défini le delegate dans awakeFromNib



1
votes

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ée, chacun avec ses propres vues et contrôleurs de détails principaux (table).

Auparavant - iOS 12.x et versions antérieures, en fait lorsque j'écrivais Objective-C - mon délégué de contrôleur de vue fractionnée était défini dans le code du contrôleur de vue principal de chaque contrôleur de vue fractionnée (parent) - J'ai défini le délégué dans la sous-classe UITableViewController de viewDidLoad procédé. 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()
        // 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 {
        ...
    }
}

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é du 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é (testé à 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 détaillée 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 détaillée. La seule solution de contournement utilisateur que j'ai trouvée qui résout ce problème est de changer l'orientation du périphérique. Ensuite, le contrôleur de vue fractionnée se comporte comme prévu.

Je pensais que cela pouvait avoir quelque chose à voir avec la nouvelle classe SceneDelegate .

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

J'ai la classe SceneDelegate personnalisée qui fonctionne parfaitement. Je le sais parce que j'ai réussi à définir une window?.tintColor dans la window?.tintColor de la 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é des commentaires à Apple et voici leur réponse modifiée ...

... le problème est que vous définissez le délégué de viewDidLoad dans un remplacement de viewDidLoad . Il est possible que UISplitViewController décide de se replier avant que quoi que ce soit ne provoque le chargement de sa vue. Quand il fait cela, 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, la synchronisation de viewDidLoad peut être imprévisible. En général, il est préférable de configurer plus tôt des éléments tels que l'affichage des délégués du contrôleur. Le faire en scene(willConnectTo: session) probablement mieux.

Ce conseil m'a beaucoup aidé.

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

    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
    }

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

J'ai changé le code pour tenter d'atteindre 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 s'il faut 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 me semblait difficile de déterminer cela dans ma classe SceneDelegate personnalisée, donc dans ma classe SceneDelegate personnalisée, j'ai écrit le code suivant à la place ...

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 {
        ...
    }

}

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

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 {
        ...
    }
}

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.


0 commentaires

0
votes

Eh bien, je pense que la réponse devrait maintenant couvrir l'iOS14.

Si vous trouvez que la méthode déléguée n'est pas appelée.

  @available(iOS 14.0, *)
  func splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column {
        return .primary
  }

peut-être devriez-vous envisager d'utiliser celui d'iOS14.

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


0 commentaires