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;
}
}
Эта реализация в основном делает следующее:
- Если
secondaryViewController
это то, что мы ожидаем (UINavigationController
), и это показывает, что мы ожидаем (DetailViewController
- ваш вид контроллера), но не имеет модели (detailItem
), затем "Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
" - В противном случае вернемся
NO
чтобы позволить контроллеру разделенного представления попытаться включить содержимое контроллера вторичного представления в свернутый интерфейс "
Результаты для iPhone в портретной ориентации следующие (начиная с портрета или с поворота на портрет - или, точнее, класса компактных размеров):
- Если ваш взгляд верен
- и есть модель, показать детальный вид контроллера
- но не имеет модели, покажите главный контроллер вида
- Если ваше мнение не верно
- показать главный контроллер вида
Ясно, как грязь.
Вот принятый ответ в 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
}
}