UISplitViewController w portrecie na iPhonie pokazuje szczegóły VC zamiast master

[1]}używam uniwersalnego storyboardu w Xcode 6, celującego w iOS 7 i wyżej. Zaimplementowałem UISplitViewController, który jest teraz natywnie obsługiwany na iPhonie z systemem iOS 8, A Xcode automatycznie dokona backportu dla iOS 7. To działa naprawdę dobrze, z wyjątkiem gdy uruchamiasz aplikację na iPhone w portret z systemem iOS 8, kontroler widoku szczegółowego dzielonego widoku jest wyświetlany, gdy spodziewałem się po raz pierwszy zobaczyć kontroler widoku głównego. Wierzyłem, że jest to błąd z iOS 8, ponieważ po uruchomieniu aplikacja na iOS 7 poprawnie pokazuje kontroler widoku głównego. ALE iOS 8 jest teraz GM i to nadal występuje. Jak skonfigurować go tak, aby gdy kontroler widoku dzielonego zostanie zwinięty (na ekranie wyświetlany jest tylko jeden kontroler widoku), gdy kontroler widoku dzielonego jest wyświetlany, pokazuje kontroler widoku głównego, a nie Szczegóły?

Stworzyłem ten kontroler widoku dzielonego w Interface Builder. Kontroler podzielonego widoku jest pierwszym kontrolerem widoku w kontrolerze paska kart. Zarówno master, jak i detail VCs są kontrolerami nawigacyjnymi z wbudowanymi kontrolerami widoku tabeli.

Author: Jordan H, 2014-09-16

14 answers

O rany, przez kilka dni bolała mnie głowa i nie mogłem wymyślić, jak to zrobić. Najgorsze było to, że tworzenie nowego projektu Xcode iOS z szablonem master-detail działało dobrze. Na szczęście, w końcu, ten mały fakt, jak znalazłem rozwiązanie.

Znalazłem kilka postów, które sugerują, że rozwiązaniem jest wdrożenie nowej metody primaryViewControllerForCollapsingSplitViewController: na UISplitViewControllerDelegate. Próbowałem tego bezskutecznie. Co Apple robi w szablonie master-detail, który wydaje się praca polega na zaimplementowaniu nowej (weź głęboki oddech, aby powiedzieć wszystko to) metody splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate(ponownie na UISplitViewControllerDelegate). Zgodnie z docs , metoda ta:

Prosi delegata o dostosowanie głównego kontrolera widoku i włączenie dodatkowego kontrolera widoku do zwiniętego interfejsu.

Zapoznaj się z częścią dyskusji tej metody, aby uzyskać więcej szczegółów.

Sposób, w jaki Apple sobie z tym radzi, to:

- (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;

    }
}

To implementacja zasadniczo wykonuje następujące czynności:

  1. Jeśli secondaryViewController jest tym, czego oczekujemy (a UINavigationController) i pokazuje to, czego oczekujemy (a DetailViewController -- twój kontroler widoku), ale nie ma modelu (detailItem), to "Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. w przeciwnym razie zwróć "NO aby kontroler dzielonego widoku spróbował włączyć zawartość kontrolera drugiego widoku do zwiniętego interfejsu "

Wyniki są następujące dla iPhone ' a w pozycji pionowej (zaczynając w pozycji pionowej lub obracając do portretu -- a dokładniej klasy wielkości kompaktowej):

  1. jeśli twój pogląd jest poprawny
    • i ma model, Pokaż kontroler widoku szczegółów
    • ale nie ma modelu, Pokaż kontroler widoku głównego
  2. jeśli twój pogląd nie jest poprawny
    • Pokaż kontroler widoku głównego
Jasne jak błoto.
 242
Author: Mark S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-17 17:12:12

Oto zaakceptowana odpowiedź w języku Swift. Po prostu utwórz tę podklasę i przypisz ją do kontrolera splitViewController w swoim storyboardzie.

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

}
 61
Author: Clifton Labrum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-01 00:10:04

Szybka wersja poprawnej odpowiedzi Marka S

Zgodnie z szablonem Master-Detail firmy 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
}

Wyjaśnienie

(to co powiedział Mark S było nieco mylące)

Ta metoda delegata nazywa się splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, ponieważ to właśnie robi. Przy zmianie na bardziej kompaktowy rozmiar szerokości (na przykład podczas obracania telefonu z poziomego na pionowy), należy zwinąć kontroler podzielonego widoku na tylko jeden z nich.

Ta funkcja zwraca wartość logiczną do zdecyduj, czy powinien zwijać szczegóły i pokazywać Master, czy nie.

Więc w naszym przypadku, zdecydujemy na podstawie tego, czy był wybrany szczegół, czy nie. Skąd wiemy, czy nasz detal został wybrany? Jeśli podążamy za szablonem Master-Detail firmy Apple, kontroler widoku detail powinien mieć opcjonalną zmienną zawierającą szczegółowe informacje, więc jeśli jest to nil (.Brak), nie ma jeszcze nic zaznaczonego i powinniśmy pokazać Master, aby użytkownik mógł coś wybrać. To wszystko.
 21
Author: NiñoScript,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
   #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
 9
Author: Gank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-17 08:47:54

Moja aplikacja została napisana w Swift 2.x i może działać dobrze. Po przekonwertowaniu go do Swift 3.0 (za pomocą konwertera XCode), zaczyna pokazywać najpierw szczegóły zamiast wzorca w trybie portretowym. Problem polega na tym, że nazwa funkcji splitViewController nie jest zmieniona, aby pasowała do nowej funkcji UISplitViewControllerDelegate.

Po ręcznej zmianie nazwy tej funkcji Moja aplikacja może teraz działać poprawnie:

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
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-03 03:48:34

Z dokumentacji , musisz użyć delegata, aby powiedzieć UISplitViewController nie włączać widoku szczegółów do "zwiniętego interfejsu" (tj. "trybu portretowego" w Twoim przypadku). W Swift 4, metoda delegata do zaimplementowania dla tego została przemianowana na:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
 9
Author: oli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 15:28:33

Jeśli nie masz domyślnych wartości do pokazania w kontrolerze widoku szczegółów, możesz po prostu usunąć domyślny segue pomiędzy SplitViewController i szczegółowym UIViewController na tablicy wątków. Spowoduje to, że zawsze trafi najpierw do kontrolera widoku głównego.

Efektem ubocznym jest to, że zamiast zobaczyć dwa widoki w krajobrazie, zobaczysz jeden widok w pełnym rozmiarze w SplitViewController aż Pokaż szczegóły Segue w master view Controller zwolniony.

 7
Author: Hao-Cher Hong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-17 03:35:46

Dla wszystkich, którzy nie mogli znaleźć piątkowego działu cs193p:

W Swifcie 3.1.1 tworzenie podklasy UISplitViewController i implementacja jednej z metod delegata zadziałało jak urok:

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

Mój storyboard

 4
Author: Bartosz Kunat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 17:09:11

Moim zdaniem powinieneś rozwiązać ten problem bardziej ogólnikowo. Możesz podklasować Kontroler UISplitViewController i zaimplementować protokół w wbudowanych kontrolerach widoku.

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
}

Przykładowa implementacja w UITableViewController:

extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool {
        return tableView.indexPathForSelectedRow == nil
    }
}
Mam nadzieję, że to pomoże. Możesz więc ponownie użyć tej klasy i po prostu zaimplementować protokół.
 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-17 22:05:07

Po prostu usuń DetailViewController ze kontrolerów SplitView, gdy potrzebujesz go do uruchomienia od 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
Author: Borys Shcherbyna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-24 22:58:50

To działało u mnie na iOS-11 i 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
Author: Vishal Chaudhry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-30 18:28:15

Funkcja jest zmieniana w nowych wersjach Swift, więc ten kod działa na 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
    }

}
 2
Author: Saeed Ir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-09 09:15:36

Xamarin / C# Solution

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
Author: Mark Moeykens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Wystarczy ustawić preferredDisplayMode właściwość UISplitViewController na .allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
 0
Author: Arash Etemad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-15 13:06:20