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é!
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é.
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:
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 queUISplitViewController
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 dansscene(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.
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
}