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é!
4 Réponses :
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 }
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é.
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
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:
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 deviewDidLoad
. Il est possible queUISplitViewController
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 enscene(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.
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 { ... }
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.