UISplitViewController в портретной ориентации на iPhone показывает детали VC вместо мастера

Я использую универсальную раскадровку в Xcode 6, ориентированную на iOS 7 и выше. Я реализовал UISplitViewController который теперь изначально поддерживается на iPhone под управлением iOS 8, и Xcode автоматически создаст бэкпорт для iOS 7. Он работает очень хорошо, за исключением того, что когда вы запускаете приложение на iPhone в портретной версии под iOS 8, контроллер подробного представления разделенного вида отображается, когда я Ожидается, что вначале увидит основной вид контроллера. Я полагал, что это было ошибкой в ​​iOS 8, потому что когда вы запускаете приложение на iOS 7, оно правильно показывает контроллер основного вида. Но iOS 8 теперь GM, и это все еще происходит. Как я могу настроить его так, чтобы в случае свертывания контроллера разделенного вида (на экране отображался только один контроллер представления), когда отображался контроллер разделенного вида, он отображал главный контроллер представления, а не детали?

Я создал этот контроллер разделенного представления в Интерфейсном Разработчике. Контроллер разделенного представления является первым контроллером представления в контроллере панели вкладок. Как ведущие, так и подробные виртуальные контроллеры являются контроллерами навигации со встроенными контроллерами табличного представления.

13 ответов

Решение

О, чувак, у меня несколько дней болела голова, и я не мог понять, как это сделать. Хуже всего было то, что создание нового проекта Xcode iOS с шаблоном master-detail работало просто отлично. К счастью, в конце концов, этот маленький факт помог мне найти решение.

Я нашел несколько сообщений, которые предполагают, что решение заключается в реализации нового primaryViewControllerForCollapsingSplitViewController: метод на UISplitViewControllerDelegate, Я пытался это безрезультатно. Что Apple делает в шаблоне master-detail, который, кажется, работает, так это внедряет новое (сделайте глубокий вдох, чтобы сказать все это) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: метод делегата (снова на UISplitViewControllerDelegate). Согласно документам, этот метод:

Просит делегата настроить основной контроллер вида и включить дополнительный контроллер вида в свернутый интерфейс.

Обязательно ознакомьтесь с дискуссионной частью этого метода для более подробной информации.

Apple это обрабатывает так:

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

    }
}

Эта реализация в основном делает следующее:

  1. Если secondaryViewController это то, что мы ожидаем (UINavigationController), и это показывает, что мы ожидаем (DetailViewController - ваш вид контроллера), но не имеет модели (detailItem), затем "Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. В противном случае вернемсяNO чтобы позволить контроллеру разделенного представления попытаться включить содержимое контроллера вторичного представления в свернутый интерфейс "

Результаты для iPhone в портретной ориентации следующие (начиная с портрета или с поворота на портрет - или, точнее, класса компактных размеров):

  1. Если ваш взгляд верен
    • и есть модель, показать детальный вид контроллера
    • но не имеет модели, покажите главный контроллер вида
  2. Если ваше мнение не верно
    • показать главный контроллер вида

Ясно, как грязь.

Вот принятый ответ в Swift. Просто создайте этот подкласс и назначьте его вашему splitViewController в вашей раскадровке.

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

}

Быстрая версия Mark S'правильный ответ

В соответствии с шаблоном Apple Master-Detail.

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
}

осветление

(То, что сказал Марк С., немного сбивало с толку)

Этот метод делегата называется splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:потому что это то, что он делает. При переходе к более компактному размеру ширины (например, при повороте телефона из альбомной в портретную ориентацию) необходимо разделить контроллер разделенного вида только на один из них.

Эта функция возвращает логическое значение, чтобы решить, следует ли свернуть деталь и показать мастер или нет.

Таким образом, в нашем случае мы решим, основываясь на том, была ли выбрана деталь или нет. Как мы узнаем, выбрана ли наша деталь? Если мы следуем шаблону Apple Master-Detail, контроллер подробного представления должен иметь необязательную переменную, содержащую подробную информацию, поэтому, если она равна nil (.None), ничего еще не выбрано, и мы должны показать Master, чтобы пользователь мог что-то выбрать.

Вот и все.

Из документации вам нужно использовать делегата, чтобы сообщить UISplitViewController не включать подробный вид в "свернутый интерфейс" (т. е. "портретный режим" в вашем случае). В Swift 4 метод делегата для реализации был переименован:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
   #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

Мое приложение было написано на Swift 2.x и могло хорошо работать. После преобразования его в Swift 3.0 (с использованием конвертера XCode) он начинает показывать сначала детали, а не мастер в портретном режиме. Проблема в том, что имя функции splitViewController не изменено, чтобы соответствовать новой функции UISplitViewControllerDelegate.

После изменения имени этой функции вручную мое приложение теперь может работать правильно:

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
}

Если у вас нет значений по умолчанию для отображения в подробном виде контроллера, вы можете просто удалить переход по умолчанию между SplitViewController и вашим подробным UIViewController в раскадровке. Это сделает его всегда первым в Master View Controller.

Побочным эффектом этого является то, что вместо просмотра двух представлений в альбомной ориентации вы увидите одно представление в полном размере в SplitViewController до тех пор, пока не будет запущена функция Show Detail Segue в главном контроллере представления.

На мой взгляд, вы должны решить эту проблему более общим. Вы можете создать подкласс UISplitViewController и реализовать протокол во встроенных контроллерах представления.

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
}

Пример реализации в UITableViewController:

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

Надеюсь, поможет. Таким образом, вы можете повторно использовать этот класс и просто нужно реализовать протокол.

Для всех людей, которые не смогли найти раздел пятницы cs193p:

В Swift 3.1.1 создание подкласса UISplitViewController и реализация одного из его методов делегатов работали для меня как обаяние:

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

Моя раскадровка

Это работало для меня на iOS-11 и 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
    }
}

Функция переименована в новых версиях Swift, поэтому этот код работает на 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
    }

}

Просто удалите DetailViewController из контроллеров SplitView, когда вам нужно запустить его с 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;
}

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

Просто установите preferredDisplayMode свойство UISplitViewController к .allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Другие вопросы по тегам