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

177

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

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

Джордан Х
источник

Ответы:

238

О, чувак, это вызывало у меня головную боль в течение нескольких дней, и я не мог понять, как это сделать. Хуже всего было то, что создание нового проекта 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. Если ваше мнение не верно
    • показать главный контроллер вида

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

Метки
источник
8
Фантастический ответ! Я просто делю на подклассы UISplitViewControllerи всегда возвращаюсь YESиз этого метода, затем просто меняю класс разделения представления в раскадровке, так как я всегда хочу показать мастер на iPhone в портретной ориентации. :)
Джордан Х
2
Я хочу, чтобы мой главный контроллер вида был скрыт, если «iPhone» находится в режиме «Портрет», потому что у меня есть настройка контроллера подробного вида по умолчанию. Как мне это сделать. Мой мастер и детали оба имеют тип VC. В частности, моя деталь - MMDrawerController. Пожалуйста, помогите
Харшит Гупта
3
Я попробовал предложение Джои о создании подкласса, UISplitViewControllerно обнаружил, что это не работает: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:никогда не звонили Вместо этого я скопировал шаблон Apple и поместил его в AppDelagate. Это потребовало внесения некоторых изменений в создание UISplitViewController, application didFinishLaunchingWithOptions:в котором я также скопировал шаблон Apple.
Ник
7
Комментарий @ joey работает с настройкой self.delegate = self; в viewdidload! И добавление <UISplitViewControllerDelegate> в .h Спасибо!
fellowworldcitizen
2
Мне кажется, это правильный ответ, потому что у меня точно такая же проблема. Тем не менее, по какой-то причине мой splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:так и не получил. Похоже, что делегат корректно устанавливает applicationDidFinishLaunchingWithOptions:метод делегата моего приложения . Кто-нибудь еще видел эту проблему и НЕ работал это решение?
Тим Дин
60

Вот принятый ответ в 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
  }

}
Клифтон Лабрум
источник
3
Отлично, это очень помогает. Но возникла новая проблема. Кнопка, которая ведет меня к мастеру, теперь исчезает (никогда не показывается). Как мне вернуть его обратно? РЕДАКТИРОВАТЬ: Неважно, понял себя :-). Для других пользователей: добавьте это в detailView: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Том Таллак Солбу,
3
Теперь в Свифте, что бы это ни былоfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Дэн Розенстарк,
2
Кажется, что этот метод делегата вызывается только тогда, когда размер класса компактен. Он вызывается на iPhone, но не на iPad-портрете, что означает, что он не решает проблему, поскольку iPad-портрет также находится в свернутом режиме. Протестировано с iOS 12.1
Daniel
21

Swift версия 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, чтобы пользователь мог что-то выбрать.

Вот и все.

NiñoScript
источник
Просто чтобы уточнить, почему я откатился от правки @ sschale. Этот код является цитатой Apple's Master-Detail template, он не предназначен, чтобы быть большим или кратким, просто фактическим. :)
NiñoScript
10

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

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
оли
источник
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
источник
9

Мое приложение было написано на 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
}
Тони
источник
У меня та же проблема, что и у вас, но я не понимаю вашего решения. Я не вижу никаких изменений в коде, который вы разместили здесь. Не могли бы Вы уточнить. Спасибо
bibscy
Многие методы слегка переименованы.
Дэйв
Ответ Тони - это синтаксис Swift 3 на ответ @ NiñoScript (который написан для предыдущих версий Swift)
Hellojeffy
2
для стрижа 3, не забудьте поставить self.delegate = selfна viewDidLoadметод.
Fer
7

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

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

Хао-Шер Хонг
источник
хороший трюк Мое приложение только в портретном режиме, и я могу это сделать.
Peacemoon
Это верно, за исключением того, что в альбомной ориентации вы увидите пустую правую часть вида, возможно, серого цвета.
Ведрано
4

Для всех людей, которые не смогли найти раздел пятницы 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
} }

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

Бартош Кунат
источник
Как отметил @olito, в Swift 4 синтаксис этого изменился на: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske
3

На мой взгляд, вы должны решить эту проблему более общим. Вы можете создать подкласс 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
    }
}

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

Maik639
источник
Метод делегата никогда не вызывается!
K_Mohit
он не вызывается на iPad и iPhone 6/7/8 Plus. Это твоя проблема? Взгляните на: stackoverflow.com/questions/29767614/…
Maik639
2

Просто удалите 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;
}
Борис Щербина
источник
2

Это сработало для меня на 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
    }
}
Вишал Чодри
источник
2

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

}
Саид Ир
источник
0

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

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

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Араш Этемад
источник