Лучший способ переключения между UISplitViewController и другими controllerами представлений?
Я создаю приложение для iPad. Один из экранов в приложении отлично подходит для использования UISplitViewController. Тем не менее, верхний уровень приложения – это главное меню, для которого я не хочу использовать UISplitViewController. Это представляет проблему, потому что Apple заявляет, что:
-
UISplitViewController
должен быть controllerом представления верхнего уровня в приложении, т.UISplitViewController
Его представление должно быть добавлено как подзаголовокUIWindow
-
если он используется,
UISplitViewController
должен присутствовать на протяжении всего срока действия приложения – т. е. не удалять его вид из UIWindow и устанавливать другой на место, или наоборот- Сделать приложение-волшебник на Android-устройстве
- как изменить пользовательский интерфейс в зависимости от выбора поля со списком
- Как определить несколько действий JButton из другого classа
- Как определить направление движения между левым / правым и вверх / вниз
- Добавление JPanels от других classов к cardLayout
Просматривая и экспериментируя, кажется, что это всего лишь жизнеспособный вариант для удовлетворения требований Apple, а наш собственный – использовать модальные диалоги. Поэтому наше приложение имеет UISplitViewController на корневом уровне (т. Е. Его представление добавлено как подзадача UIWindow), и чтобы показать наше главное меню, мы нажимаем его как полноэкранный модальный диалог на UISplitViewController. Затем, отклонив модальное диалоговое окно диспетчера меню главного меню, мы можем фактически показать наше разделенное представление.
Эта страtagsя работает нормально. Но он задает вопросы:
1) Есть ли лучший способ структурирования этого, без модалов, который также отвечает всем указанным требованиям? Кажется немного странным, если основной пользовательский интерфейс появляется из-за того, что его вытесняют как модальный диалог. (Предполагается, что модалы предназначены для целенаправленных задач пользователя.)
2) Есть ли у меня риск отказа магазина приложений из-за моего подхода? Эта модальная страtagsя, вероятно, «неправильно использует» модальные диалоги, в соответствии с руководящими принципами человеческого интерфейса Apple. Но какой другой выбор они мне дали? Знают ли они, что я это делаю?
- Добавление полосы прокрутки в подзаголовки в графическом интерфейсе
- Выберите все содержимое текстового поля, когда он получает фокус (Vanilla JS или jQuery)
- Как программно перемещать UIScrollView для фокусировки в контроле над клавиатурой?
- Слушатель модели JTable слишком быстро обнаруживает вставленные строки (до их рисования)
- Переместить макеты, когда отображается мягкая клавиатура?
- Переопределить цвета контекстного меню в Android
- GUI работает со скоростью 30 кадров в секунду?
- Почему большинство пользовательских интерфейсов однопоточных?
Touche! Подходите к той же проблеме и решили ее так же, используя модалы. В моем случае это был вид входа в систему, а затем главное меню, которое должно быть показано перед splitview. Я использовал ту же страtagsю, что и вы. Я (как и многие другие знающие iOS люди, с которыми я говорил) не смог найти лучшего выхода. Прекрасно работает для меня. Пользователь никогда не замечает модальность. Представьте их так. И да, я также могу сказать вам, что есть довольно много приложений, которые делают то же самое под трюками капюшона в магазине приложений. 🙂 Еще одна заметка, дайте мне знать, если вы когда-нибудь каким-нибудь образом выберете какой-нибудь способ:
Я серьезно не верил, что это представление о том, что некоторый UIViewController должен отображаться перед UISplitViewController (например, форма входа в систему), оказывается настолько сложной, пока мне не пришлось создавать такой вид hiearchy.
Мой пример основан на iOS 8 и XCode 6.0 (Swift), поэтому я не уверен, существовала ли эта проблема раньше, или это связано с некоторыми новыми ошибками, введенными в iOS 8, но из всех подобных вопросов я нашел, я не видел полного «не очень взломанного» решения этой проблемы.
Я проведу вас через некоторые из вещей, которые я пробовал, прежде чем я закончил решение (в конце этого сообщения). Каждый пример основан на создании нового проекта из шаблона Master-Detail без включенного CoreData.
Сначала попробуйте (modal segue для UISplitViewController):
- создать новый подclass UIViewController (например, LoginViewController)
- добавьте новый controller представлений в раскадровку, установите его как начальный controller представления (вместо UISplitViewController) и подключите его к LoginViewController
- добавьте UIButton в LoginViewController и создайте modal segue с этой кнопки на UISplitViewController
- скопируйте код установки шаблона для UISplitViewController из приложения AddDinlegate,
didFinishLaunchingWithOptions
в AppDelegate, вprepareForSegue
CreateViewSearch дляprepareForSegue
Это почти сработало. Я говорю почти, потому что после того, как приложение запускается с LoginViewController, и вы нажимаете кнопку и переходите к UISplitViewController, происходит странная ошибка: показ и скрытие главного controllerа просмотра при изменении ориентации больше не анимируется.
После некоторого времени, борющегося с этой проблемой и без реального решения, я подумал, что это как-то связано с тем странным правилом, что UISplitViewController должен быть rootViewController (и в этом случае он не является, LoginViewController), поэтому я отказался от этого не столь совершенного решения ,
Вторая попытка (модальный переход от UISplitViewController):
- создать новый подclass UIViewController (например, LoginViewController)
- добавить новый controller представления в раскадровку и подключить его к LoginViewController (но на этот раз оставить UISplitViewController в качестве начального controllerа представления)
- создать modal segue из UISplitViewController в LoginViewController
- добавьте UIButton в LoginViewController и создайте разворот из этой кнопки
Наконец, добавьте этот код в AppDelegate’s didFinishLaunchingWithOptions
после шаблона кода для настройки UISplitViewController:
window?.makeKeyAndVisible() splitViewController.performSegueWithIdentifier("segueToLogin", sender: self) return true
или попробуйте с этим кодом:
window?.makeKeyAndVisible() let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController splitViewController.presentViewController(loginViewController, animated: false, completion: nil) return true
Оба этих примера производят одни и те же самые плохие вещи:
- консольные выходы:
Unbalanced calls to begin/end appearance transitions for
- UISplitViewController должен быть показан первым, прежде чем LoginViewController будет изменен модально (я бы скорее представил только регистрационную форму, чтобы пользователь не видел UISplitViewController перед входом в систему)
- Unwind segue не называется (это совсем другая ошибка, и я не буду входить в эту историю сейчас)
Решение (обновление rootViewController)
Единственный способ, который я нашел, который работает правильно, – это если вы измените rootViewController windows на лету:
- Определите идентификатор раскадровки для LoginViewController и UISplitViewController и добавьте какое-то свойство loggedIn в AppDelegate.
- Основываясь на этом свойстве, создайте соответствующий controller представления и после этого установите его как rootViewController.
- Сделайте это без анимации в
didFinishLaunchingWithOptions
но анимированный при вызове из пользовательского интерфейса.
Вот пример кода из AppDelegate:
var loggedIn = false func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { setupRootViewController(false) return true } func setupRootViewController(animated: Bool) { if let window = self.window { var newRootViewController: UIViewController? = nil var transition: UIViewAnimationOptions // create and setup appropriate rootViewController if !loggedIn { let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController newRootViewController = loginViewController transition = .TransitionFlipFromLeft } else { let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem() splitViewController.delegate = self let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController let controller = masterNavigationController.topViewController as MasterViewController newRootViewController = splitViewController transition = .TransitionFlipFromRight } // update app's rootViewController if let rootVC = newRootViewController { if animated { UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in window.rootViewController = rootVC }, completion: nil) } else { window.rootViewController = rootVC } } } }
И это пример кода из LoginViewController:
@IBAction func login(sender: UIButton) { let delegate = UIApplication.sharedApplication().delegate as AppDelegate delegate.loggedIn = true delegate.setupRootViewController(true) }
Я также хотел бы услышать, есть ли лучший / более чистый способ для правильной работы в iOS 8.
И кто сказал, что у вас может быть только одно окно? 🙂
Посмотрите, может ли мой ответ на этот похожий вопрос помочь.
Этот подход работает очень хорошо для меня. До тех пор, пока вам не нужно беспокоиться о нескольких экранах или восстановлении состояния, этот связанный код должен быть достаточным для того, чтобы сделать то, что вам нужно: вам не нужно заставлять свою логику смотреть назад или переписывать существующий код и все еще можете воспользоваться UISplitView на более глубоком уровне в вашем приложении – без (AFAIK), нарушающего принципы Apple.
Для будущих разработчиков iOS, работающих в одной и той же проблеме: вот еще один ответ и объяснения. Вы должны сделать это корневым controllerом. Если это не так, наложите модальный.
UISplitviewcontroller не является controllerом rootview
Просто столкнулся с этой проблемой в проекте и решил поделиться своим решением. В нашем случае (для iPad) мы хотели начать с UISplitViewController
с UISplitViewController
вида (с использованием preferredDisplayMode = .allVisible
). В какой-то момент иерархии деталей (справа) (у нас также был controller навигации для этой стороны), мы хотели направить новый controller представления на весь controller разделенного представления (не использовать модальный переход).
На iPhone это поведение предоставляется бесплатно, поскольку в любой момент видится только один controller вида. Но на iPad нам нужно было что-то выдумать. Мы закончили работу с controllerом представлений корневого контейнера, который добавляет к нему controller вида просмотра в виде controllerа детского представления. Этот controller корневого представления встроен в controller навигации. Когда controller подробного представления в controllerе разделенного представления хочет нажать новый controller на весь controller разделенного вида, controller корневого представления подталкивает этот новый controller представления своим навигационным controllerом.
Я хотел бы внести свой вклад в представление UISplitViewController, так как вам может понравиться через -presentViewController:animated:completion:
(мы все знаем, что это не сработает). Я создал подclass UISplitViewController, который отвечает на:
-presentAsRootViewController -returnToPreviousViewController
Класс, который, как и другие успешные подходы, устанавливает UISplitViewController как rootViewController windows, но делает это с анимацией, подобной той, которую вы получаете (по умолчанию) с -presentViewController:animated:completion:
PresentableSplitViewController.h
#import @interface PresentableSplitViewController : UISplitViewController - (void) presentAsRootViewController; @end
PresentableSplitViewController.m
#import "PresentableSplitViewController.h" @interface PresentableSplitViewController () @property (nonatomic, strong) UIViewController *previousViewController; @end @implementation PresentableSplitViewController - (void) presentAsRootViewController { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; _previousViewController=window.rootViewController; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = self; [window insertSubview:windowSnapShot atIndex:0]; CGRect dstFrame=self.view.frame; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=self.view.frame.size.width; offset.height*=self.view.frame.size.height; self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.view.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; }]; } - (void) returnToPreviousViewController { if(_previousViewController) { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = _previousViewController; [window addSubview:windowSnapShot]; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=windowSnapShot.frame.size.width; offset.height*=windowSnapShot.frame.size.height; CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ windowSnapShot.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; _previousViewController=nil; }]; } } @end
Я сделал UISplitView в качестве начального представления, чем он имеет модально для полноэкранного UIView и обратно в UISplitView. Если вам нужно вернуться в SplitView, вы должны использовать пользовательский сеанс.
Прочтите эту ссылку (переведите ее с японского)
UIViewController для UISplitViewController
Добавляя к ответу @tadija, я в подобной ситуации:
Мое приложение предназначалось только для телефонов, и я добавляю пользовательский интерфейс планшета. Я решил сделать это в Swift в том же приложении – и, в конечном счете, перенести все приложение, чтобы использовать тот же раскадровки (когда я чувствую, что версия IPad стабильна, ее использование для телефонов должно быть тривиальным с новыми classами из XCode6).
На моей сцене еще не было определений, но это все еще работает.
Мой код в моем делете приложения находится в ObjectiveC и немного отличается – но использует ту же идею. Обратите внимание, что я использую controller представлений по умолчанию из сцены, в отличие от предыдущих примеров. Я чувствую, что это также будет работать на IOS7 / IPhone, в котором среда выполнения будет генерировать обычный UINavigationController
вместо UISplitViewController
. Я мог бы даже добавить новый код, который будет вызывать controller входа в систему на IPhones, вместо изменения rootVC.
- (void) setupRootViewController:(BOOL) animated { UIViewController *newController = nil; UIStoryboard *board = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve; if (!loggedIn) { newController = [board instantiateViewControllerWithIdentifier:@"LoginViewController"]; } else { newController = [board instantiateInitialViewController]; } if (animated) { [UIView transitionWithView: self.window duration:0.5 options:transition animations:^{ self.window.rootViewController = newController; NSLog(@"setup root view controller animated"); } completion:^(BOOL finished) { NSLog(@"setup root view controller finished"); }]; } else { self.window.rootViewController = newController; } }
Еще один вариант: В controllerе представления подробностей я показываю controller модального представления:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate if (!appDelegate.loggedIn) { // display the login form let storyboard = UIStoryboard(name: "Storyboard", bundle: nil) let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController self.presentViewController(login, animated: false, completion: { () -> Void in // user logged in and is valid now self.updateDisplay() }) } else { updateDisplay() }
Не отклоняйте controller входа, не устанавливая флаг входа. Обратите внимание, что в IPhones controller главного представления будет первым, так что очень похожий код должен находиться на controllerе главного представления.