UISplitViewController w pionie na iPhonie pokazuje szczegóły VC zamiast wzorca

177

Używam Universal Storyboard w Xcode 6, przeznaczonego dla iOS 7 i nowszych. Zaimplementowałem program, UISplitViewControllerktóry jest teraz natywnie obsługiwany na iPhonie z systemem iOS 8, a Xcode automatycznie utworzy backport na iOS 7. Działa naprawdę dobrze, z wyjątkiem sytuacji, gdy uruchamiasz aplikację na iPhonie w pionie z systemem iOS 8, widok szczegółów podzielonego widoku kontroler jest wyświetlany, gdy spodziewałem się zobaczyć główny kontroler widoku. Uważałem, że to błąd w iOS 8, ponieważ po uruchomieniu aplikacji na iOS 7 poprawnie pokazuje kontroler widoku głównego. Ale iOS 8 jest teraz GM i to nadal się dzieje. Jak mogę to ustawić tak, aby kiedy kontroler widoku podzielonego miał być zwinięty (na ekranie wyświetlany był tylko jeden kontroler widoku), kiedy wyświetlany jest kontroler widoku podzielonego, pokazuje kontroler widoku głównego, a nie szczegóły?

Stworzyłem ten kontroler widoku podzielonego w Interface Builder. Kontroler widoku podzielonego jest pierwszym kontrolerem widoku w kontrolerze paska kart. Zarówno główny, jak i szczegółowy VC są kontrolerami nawigacji z wbudowanymi kontrolerami widoku tabeli.

Jordan H.
źródło

Odpowiedzi:

238

O rany, to powodowało u mnie ból głowy przez kilka dni i nie mogłem wymyślić, jak to zrobić. Najgorsze było to, że stworzenie nowego projektu Xcode na iOS z szablonem master-detail działało dobrze. Na szczęście ostatecznie ten mały fakt polegał na tym, jak znalazłem rozwiązanie.

Znalazłem kilka postów sugerujących, że rozwiązaniem jest zaimplementowanie nowej primaryViewControllerForCollapsingSplitViewController:metody na UISplitViewControllerDelegate. Próbowałem tego bezskutecznie. To, co Apple robi w szablonie master-detail, który wydaje się działać, to zaimplementowanie nowej (weź głęboki oddech, aby powiedzieć wszystko to) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:metoda delegowania (ponownie włączona UISplitViewControllerDelegate). Według dokumentacji ta metoda:

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

Koniecznie przeczytaj część dyskusyjną tej metody, aby uzyskać bardziej szczegółowe informacje.

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

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

    }
}

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

  1. Jeśli secondaryViewControllerjest to, czego oczekujemy (a UINavigationController) i pokazuje to, czego oczekujemy (a DetailViewController- 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 wróć „, NOaby umożliwić kontrolerowi podzielonego widoku próbę włączenia zawartości dodatkowego kontrolera widoku do zwiniętego interfejsu”

Wyniki są następujące dla iPhone'a w orientacji pionowej (zaczynając w orientacji pionowej lub obracając się do portretu - lub dokładniej w klasie kompaktowej):

  1. Jeśli twój pogląd jest poprawny
    • i ma model, pokaż kontroler widoku szczegółowego
    • ale nie ma modelu, pokaż główny kontroler widoku
  2. Jeśli twój pogląd nie jest poprawny
    • pokaż główny kontroler widoku

Czyste jak błoto.

Znaki
źródło
8
Fantastyczna odpowiedź! Po prostu podklasowałem UISplitViewControlleri zawsze wracałem YESz tej metody, a potem po prostu zmieniłem klasę podzielonego widoku w Storyboard, ponieważ zawsze chcę pokazać wzorzec na iPhonie w pionie. :)
Jordan H
2
Chcę, aby mój główny kontroler widoku był ukryty, jeśli „iPhone” jest w trybie „Portret”, ponieważ mam domyślną konfigurację kontrolera widoku szczegółowego. Jak mogę to zrobić. Mój mistrz i szczegół są typu VC. W szczególności mój szczegół to MMDrawerController. Proszę o pomoc
Harshit Gupta
3
Wypróbowałem sugestię Joeya dotyczącą podklasy, UISplitViewControllerale okazało się, że to nie zadziałało: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:nigdy nie został wezwany. Zamiast tego skopiowałem szablon Apple i umieściłem go w AppDelagate. To wymagało kilku zmian w tworzeniu UISplitViewController również pod application didFinishLaunchingWithOptions:(gdzie również skopiowałem szablon Apple).
Nick
7
Komentarz @ joey działa z ustawieniem self.delegate = self; w widoku viewdidload! I dodanie <UISplitViewControllerDelegate> w .h Thankyou!
fellowworldcitizen
2
Wydaje mi się, że to właściwa odpowiedź, ponieważ mam dokładnie ten sam problem. Jednak z jakiegoś powodu splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:nigdy nie zostałem wezwany. Wygląda na to, że delegat jest poprawnie ustawiany applicationDidFinishLaunchingWithOptions:metoda delegata aplikacji . Czy ktoś inny widział ten problem i NIE miał tego rozwiązania?
Tim Dean
60

Oto akceptowana odpowiedź w języku Swift. Po prostu utwórz tę podklasę i przypisz ją do swojego 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
  }

}
Clifton Labrum
źródło
3
Świetnie, to bardzo pomaga. Ale pojawił się nowy problem. Przycisk wstecz, który przenosi mnie do mistrza, teraz znika (nigdy się nie pojawia). Jak to odzyskać? EDYCJA: Nieważne, pomyślałem sobie :-). Dla innych użytkowników: dodaj to w szczegółachView: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu
3
Teraz w Swift cokolwiek to jestfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark
2
Wygląda na to, że metoda delegata jest wywoływana tylko wtedy, gdy rozmiar klasy jest niewielki. Jest wywoływany na iPhonie, ale nie na iPadzie portret, co oznacza, że ​​nie rozwiązuje problemu, ponieważ portret iPada również jest zwinięty. Testowane z iOS 12.1
Daniel,
21

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 zagmatwane)

Ta metoda delegata jest wywoływana splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, ponieważ to właśnie robi. W przypadku zmiany na mniejszy rozmiar szerokości (na przykład podczas obracania telefonu z orientacji poziomej do pionowej), należy zwinąć kontroler widoku podzielonego na tylko jeden z nich.

Ta funkcja zwraca wartość logiczną, aby zdecydować, czy powinna zwinąć szczegół i pokazać wzorzec, czy nie.

W naszym przypadku zdecydujemy się na podstawie tego, czy wybrano jakiś szczegół, czy nie. Skąd wiemy, czy wybrany został nasz szczegół? Jeśli postępujemy zgodnie z szablonem Master-Detail firmy Apple, kontroler widoku szczegółów powinien mieć opcjonalną zmienną zawierającą szczegółowe informacje, więc jeśli jest to zero (.None), nic nie zostało jeszcze wybrane i powinniśmy pokazać Master, aby użytkownik mógł coś wybrać.

Otóż ​​to.

NiñoScript
źródło
Żeby wyjaśnić, dlaczego wycofałem się z edycji @ sschale. Ten kod jest cytatem Apple's Master-Detail template, nie ma być świetny ani zwięzły, tylko oparty na faktach. :)
NiñoScript
10

Z dokumentacji musisz użyć delegata, aby powiedzieć, że UISplitViewController ma nie włączać widoku szczegółowego do „zwiniętego interfejsu” (tj. „Trybu portretowego” w twoim przypadku). W języku Swift 4 nazwa metody delegata do zaimplementowania została zmieniona:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
oli
źródło
9
   #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
Gank
źródło
9

Moja aplikacja została napisana w języku Swift 2.x i mogła działać dobrze. Po przekonwertowaniu go do Swift 3.0 (przy użyciu konwertera XCode), zaczyna wyświetlać najpierw szczegóły zamiast wzorca w trybie portretowym. Problem polega na tym, że nazwa funkcji splitViewController nie jest zmieniana, aby pasowała do nowej z 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
}
Tony
źródło
Mam ten sam problem co Ty, ale nie rozumiem Twojego rozwiązania. Nie widzę żadnych zmian w zamieszczonym tutaj kodzie. Możesz być bardziej dokładny. Dzięki
bibscy
Nazwy wielu metod zostały nieznacznie zmienione.
Dave
Odpowiedzią Tony'ego jest składnia Swift 3 na odpowiedź @ NiñoScript (która jest napisana dla poprzednich wersji Swift)
Hellojeffy
2
dla szybkich 3, nie zapomnij umieścić self.delegate = selfna viewDidLoadmetodzie.
Fer
7

Jeśli nie masz wartości domyślnych do pokazania w kontrolerze widoku szczegółowego, możesz po prostu usunąć domyślny przepływ między SplitViewController i szczegółowym UIViewController na tablicy scenariuszy. Dzięki temu zawsze najpierw trafi do Master View Controller.

Efektem ubocznym tego jest to, że zamiast widzieć dwa widoki w poziomie, zobaczysz jeden widok w pełnym rozmiarze w SplitViewController do momentu uruchomienia kontrolera Pokaż szczegóły płynne w kontrolerze widoku głównego.

Hao-Cher Hong
źródło
dobry trik. Moja aplikacja jest tylko w trybie portretowym i mogę to zrobić.
Peacemoon
Jest to prawdą, z wyjątkiem tego, że w orientacji poziomej zobaczysz pustą prawą część widoku, która może być wypełniona szaro.
vedrano
4

Dla wszystkich, którzy nie mogli znaleźć piątkowej sekcji cs193p:

W Swift 3.1.1 utworzenie podklasy UISplitViewController i zaimplementowanie jednej z jej delegowanych metod działało dla mnie 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 scenorys

Bartosz Kunat
źródło
Jak zauważył @olito, w Swift 4 składnia zmieniła się na: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske
3

Moim zdaniem powinieneś rozwiązać ten problem bardziej ogólny. Możesz podklasować UISplitViewController i zaimplementować protokół w osadzonych 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ół.

Maik639
źródło
Metoda delegata nigdy nie jest wywoływana!
K_Mohit
nie jest wywoływany na iPadzie i iPhonie 6/7/8 Plus. Czy to twój problem? Spójrz na: stackoverflow.com/questions/29767614/…
Maik639
2

Po prostu usuń DetailViewController z kontrolerów SplitView, gdy potrzebujesz go do uruchomienia z poziomu głównego.

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;
}
Borys Shcherbyna
źródło
2

To zadziałało dla 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
    }
}
Vishal Chaudhry
źródło
2

Nazwa funkcji została zmieniona w nowych wersjach Swift, więc ten kod działa w 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
    }

}
Saeed Ir
źródło
0

Rozwiązanie 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;
    }
}
Mark Moeykens
źródło
0

Po prostu ustaw preferredDisplayModewłaściwość UISplitViewControllerto.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Arash Etemad
źródło