web-dev-qa-db-fra.com

UISplitViewController en portrait sur iPhone montre les détails VC au lieu de maître

J'utilise un scénario universel dans Xcode 6, ciblant iOS 7 et les versions ultérieures. J'ai implémenté une UISplitViewController qui est maintenant prise en charge de manière native sur l'iPhone sous iOS 8 et Xcode le restitue automatiquement pour iOS 7. Cela fonctionne très bien, sauf lorsque vous lancez l'application sur iPhone en mode portrait sous iOS 8, les détails de la vue fractionnée Le contrôleur de vue est affiché lorsque je pensais voir pour la première fois le contrôleur de vue principal. Je pensais que c'était un bogue avec iOS 8 car, lorsque vous exécutez l'application sur iOS 7, le contrôleur de vue principal s'affiche correctement. Mais iOS 8 est maintenant GM et cela se produit toujours. Comment puis-je le configurer pour que, lorsque le contrôleur de vue fractionnée va être réduit (un seul contrôleur de vue affiché à l'écran), lorsque le contrôleur de vue fractionnée est affiché, il affiche le contrôleur de vue principal et non les détails?

J'ai créé ce contrôleur de vue fractionnée dans Interface Builder. Le contrôleur de vue fractionnée est le premier contrôleur de vue dans un contrôleur de barre d'onglets. Les VC principaux et détaillés sont tous deux des contrôleurs de navigation avec des contrôleurs de vue de table intégrés.

164
Jordan H

Oh mec, cela me causait un mal de tête pendant quelques jours et je ne savais pas comment faire cela. Le pire, c’est que la création d’un nouveau projet Xcode iOS avec le modèle maître-détail a parfaitement fonctionné. Heureusement, finalement, ce petit fait a été la façon dont j'ai trouvé la solution.

Certains articles suggèrent que la solution consiste à implémenter la nouvelle méthode primaryViewControllerForCollapsingSplitViewController: sur UISplitViewControllerDelegate. J'ai essayé cela en vain. Dans le modèle maître-détail qui semble fonctionner, Apple met en œuvre la nouvelle méthode splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate (à nouveau sur UISplitViewControllerDelegate). Selon les docs , cette méthode:

Demande au délégué d'ajuster le contrôleur de vue principal et d'intégrer le contrôleur de vue secondaire dans l'interface réduite.

Assurez-vous de lire la partie discussion de cette méthode pour plus de détails.

La façon dont Apple gère cela est la suivante:

- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]]
        && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}

Cette implémentation a essentiellement les effets suivants:

  1. Si secondaryViewController correspond à ce que nous attendons (un UINavigationController) et qu'il indique ce que nous attendons (un DetailViewController - votre contrôleur de vue), mais n'a pas de modèle (detailItem), alors "Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. Sinon, retournez "NO pour laisser le contrôleur de vue fractionnée essayer d'incorporer le contenu du contrôleur de vue secondaire dans l'interface réduite"

Les résultats sont les suivants pour l'iPhone en portrait (débutant en portrait ou tournant en portrait - ou plus précisément en taille compacte):

  1. Si votre vue est correcte
    • et a un modèle, montre le contrôleur de vue de détail
    • mais n'a pas de modèle, affiche le contrôleur de vue principal
  2. Si votre vue n'est pas correcte
    • affiche le contrôleur de vue principal

Clair comme de la boue.

226
Mark S

Voici la réponse acceptée dans Swift. Créez simplement cette sous-classe et assignez-la à votre splitViewController dans votre storyboard.

//GlobalSplitViewController.Swift

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.delegate = self
  }

  func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{
    return true
  }

}
58
Clifton Labrum

Version rapide de la réponse correcte de Mark S

Tel que fourni par le modèle Master-Detail d’Apple.

func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.detailItem == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

Clarification

(Ce que Mark S a dit était légèrement déroutant)

Cette méthode de délégué s'appelle splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, car c'est ce qu'elle fait. Lorsque vous modifiez une largeur plus compacte (par exemple, lorsque vous faites pivoter le téléphone de paysage à portrait), le contrôleur de vue fractionnée doit être réduit en un seul.

Cette fonction retourne un booléen pour décider si elle doit réduire le détail et afficher le maître ou non.

Donc, dans notre cas, nous déciderons si un détail a été sélectionné ou non. Comment savons-nous si notre détail est sélectionné? Si nous suivons le modèle Maître-Détails d’Apple, le contrôleur de vue de détail devrait avoir une variable facultative contenant les informations de détail. Par conséquent, si elle est nulle (.None), rien n’a encore été sélectionné et nous devons montrer au maître que l’utilisateur peut sélectionner quelque chose.

C'est tout.

19
NiñoScript

Mon application a été écrite dans Swift 2.x et pourrait bien fonctionner. Après l'avoir converti en Swift 3.0 (à l'aide du convertisseur XCode), il commence par afficher les détails plutôt que le mode principal en mode portrait. Le problème est que le nom de la fonction splitViewController n'est pas modifié pour correspondre au nouveau de UISplitViewControllerDelegate.

Après avoir changé le nom de cette fonction manuellement, mon application peut maintenant fonctionner correctement:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.game == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}
9
Tony

À partir de documentation , vous devez utiliser un délégué pour demander à la UISplitViewControllernot d’incorporer la vue de détail dans "l’interface réduite" (c’est-à-dire le "mode Portrait" dans votre cas). Dans Swift 4, la méthode déléguée à implémenter a été renommée:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
8
olito
   #import <UIKit/UIKit.h>

    @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate>




    @end

.m:

#import "SplitProductView.h"
#import "PriceDetailTableView.h"

@interface SplitProductView ()

@end

@implementation SplitProductView

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[PriceDetailTableView class]]

        //&& ([(PriceDetailTableView *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)

        ) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}
@end
8
Gank

Si vous n'avez pas de valeurs par défaut à afficher dans le contrôleur de vue de détail, vous pouvez simplement supprimer le passage par défaut entre SplitViewController et votre UIViewController de détail dans le story-board.

L'effet secondaire de cela est qu'au lieu de voir deux vues en paysage, vous verrez une vue en taille réelle dans SplitViewController jusqu'à ce que la séquence Afficher les détails dans le contrôleur de vue principale soit activée.

7
Hao-Cher Hong

Pour toutes les personnes qui n'ont pas pu trouver la section vendredi de cs193p: 

Dans Swift 3.1.1, créer une sous-classe de UISplitViewController et implémenter l'une de ses méthodes de délégation fonctionnait pour moi comme un charme: 

class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
}

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

Mon storyboard

3
Bartosz Kunat

Supprimez simplement DetailViewController des contrôleurs SplitView lorsque vous avez besoin de démarrer à partir de Master.

UISplitViewController *splitViewController = (UISplitViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SETTINGS"];
splitViewController.delegate = self;
[self.navigationController presentViewController:splitViewController animated:YES completion:nil];
if (IPHONE) {
    NSMutableArray * cntlrs = [splitViewController.viewControllers mutableCopy];
    [cntlrs removeLastObject];
    splitViewController.viewControllers = cntlrs;
}
2
Borys Shcherbyna

Cela a fonctionné pour moi sur iOS-11 et Swift 4:

//Following code in application didFinishLaunching (inside Application Delegate)
guard let splitViewController = window?.rootViewController as? UISplitViewController,
            let masterNavVC = splitViewController.viewControllers.first as? UINavigationController,
            let masterVC = masterNavVC.topViewController as? MasterViewController
else { fatalError() }
splitViewController.delegate = masterVC

//Following code in MasterViewController class
extension MasterViewController:UISplitViewControllerDelegate {
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }
}
2
Vishal Chaudhry

À mon avis, vous devriez résoudre ce problème de manière plus générique. Vous pouvez sous-classer UISplitViewController et implémenter un protocole dans les contrôleurs de vue intégrés.

class MasterShowingSplitViewController: UISplitViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension MasterShowingSplitViewController: UISplitViewControllerDelegate {
    func splitViewController(splitViewController: UISplitViewController,
                             collapseSecondaryViewController secondaryViewController: UIViewController,
                             ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
        guard let masterNavigationController = primaryViewController as? UINavigationController,
                  master = masterNavigationController.topViewController as? SplitViewControllerCollapseProtocol else {
            return true
        }
        return master.shouldShowMasterOnCollapse()

    }
}

protocol SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool
}

Exemple d'implémentation dans UITableViewController:

extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool {
        return tableView.indexPathForSelectedRow == nil
    }
}

J'espère que cela vous aidera ... Vous pourrez ainsi réutiliser cette classe et vous aurez juste besoin de mettre en oeuvre un protocole.

2
Maik639

Il suffit de définir la propriété preferredDisplayMode de UISplitViewController sur .allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
0
Arash Etemad

Solution Xamarin/C #

public partial class MainSplitViewController : UISplitViewController
{
    public MainSplitViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        Delegate = new MainSplitViewControllerDelegate();
    }
}

public class MainSplitViewControllerDelegate : UISplitViewControllerDelegate
{
    public override bool CollapseSecondViewController(UISplitViewController splitViewController, UIViewController secondaryViewController, UIViewController primaryViewController)
    {
        return true;
    }
}
0
Mark Moeykens

La fonction est renommée dans les nouvelles versions de Swift. Ce code fonctionne donc sur Swift 4:

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

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

}
0
Pink Panther