Вместо push segue как заменить контроллер просмотра (или удалить из стека навигации)?
У меня небольшое приложение для iPhone, в котором навигационный контроллер отображает 3 вида (здесь полноэкранное):
![Xcode screenshot]()
Сначала он отображает список социальных сетей (Facebook, Google+ и т.д.):
![list screenshot]()
Затем он отображает диалоговое окно OAuth с запросом учетных данных:
![login screenshot]()
И (после этого в том же UIWebView
) для разрешений:
![permissions screenshot]()
Наконец, он отображает последний контроллер представления с данными пользователя (в реальном приложении это будет меню, в котором можно запустить многопользовательскую игру):
![details screenshot]()
Все это работает хорошо, но у меня есть проблема, когда пользователь хочет вернуться и выбрать другую социальную сеть:
Пользователь обращается к кнопке "Назад" и вместо отображения первого представления отображается вторая, запрашивая учетные данные/разрешения OAuth снова.
Что я могу сделать здесь? Xcode 5.0.2 показывает очень ограниченный выбор для segues - push, модальный (который я не могу использовать, потому что он скрывает навигационную панель, необходимую для моей игры) и заказ.
Я новичок в программировании iOS, но ранее я разработал мобильное приложение Adobe AIR, и там было возможно: 1) заменить вид вместо и 2) удалить ненужный вид из стека навигации.
Как сделать то же самое в родном приложении?
Ответы
Ответ 1
Вы можете использовать пользовательский segue: для этого вам нужно создать подкласс класса UIStoryboardSegue (пример MyCustomSegue), а затем вы можете переопределить "выполнить" что-то вроде этого
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Pop to root view controller (not animated) before pushing
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:destinationController animated:YES];
}
В этот момент перейдите в Interface Builder, выберите "custom" segue и поместите имя своего класса (пример MyCustomSegue)
Ответ 2
Чтобы развернуть на разных уровнях выше, это мое решение. Он имеет следующие преимущества:
- Может работать в любом месте стека представлений, а не только в верхнем виде (не уверен, что это реально когда-либо необходимо или даже технически возможно запускать, но он его там).
- Это не приводит к переходу по умолчанию к предыдущему контроллеру представления перед отображением замены, он просто отображает новый контроллер с естественным переходом, при этом обратная навигация переходит к той же обратной навигации контроллера источника.
Код Segue:
- (void)perform {
// Grab Variables for readability
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Get a changeable copy of the stack
NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
// Replace the source controller with the destination controller, wherever the source may be
[controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];
// Assign the updated stack with animation
[navigationController setViewControllers:controllerStack animated:YES];
}
Ответ 3
Пользовательский переход не работал для меня, так как у меня был контроллер представления Splash, и я хотел заменить его. Поскольку в списке был только один контроллер представления, popToRootViewController
прежнему оставлял Splash в стеке. Я использовал следующий код для замены одного контроллера
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
[navigationController setViewControllers:@[destinationController] animated:YES];
}
а теперь в Swift 4:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
source.navigationController?.setViewControllers([destination], animated: true)
}
}
и теперь в Swift 2.0
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
}
}
Ответ 4
оперативная версия ima747:
override func perform() {
let navigationController: UINavigationController = sourceViewController.navigationController!;
var controllerStack = navigationController.viewControllers;
let index = controllerStack.indexOf(sourceViewController);
controllerStack[index!] = destinationViewController
navigationController.setViewControllers(controllerStack, animated: true);
}
Как он упомянул, он имеет следующие преимущества:
- Может работать в любом месте стека представлений, а не только в верхнем виде (не уверен, что это реально когда-либо необходимо или даже технически возможно запускать, но он его там).
- Это не приводит к переходу по умолчанию к предыдущему контроллеру представления перед отображением замены, он просто отображает новый контроллер с естественным переходом, при этом обратная навигация переходит к той же обратной навигации контроллера источника.
Ответ 5
Для этой проблемы, я думаю, ответ прост, как
- Получить массив контроллеров представлений из NavigationController
- Удаление последнего ViewController (текущий контроллер представления)
- Вставьте новый файл
-
Затем установите массив ViewControllers обратно в navigationController
как ниже:
if let navController = self.navigationController {
let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
var stack = navController.viewControllers
stack.remove(at: stack.count - 1) // remove current VC
stack.insert(newVC, at: stack.count) // add the new one
navController.setViewControllers(stack, animated: true) // boom!
}
отлично работает с Swift 3.
Надеюсь, это поможет некоторым новым парням.
Приветствия.
Ответ 6
Использование разматывания segue было бы наиболее подходящим решением этой проблемы. Я согласен с Лауро.
Ниже приведено краткое объяснение для настройки размотки segue из detailsViewController [или viewController3] в myAuthViewController [или viewController1]
Это, по сути, то, как вы могли бы выполнять размотку segue через код.
-
Внедрите метод IBAction в viewController, который вы хотите отключить (в этом случае viewController1). Имя метода может быть любым так долго, что требуется один аргумент типа UIStoryboardSegue.
@IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
println("segue with ID: %@", segue.Identifier)
}
-
Свяжите этот метод в viewController (3), который вы хотите отключить. Чтобы связать, щелкните правой кнопкой мыши (двойной щелчок пальцем) на значке выхода в верхней части viewController, в этот момент метод "unwindToMyAuth" появится во всплывающем окне. Управляйте кликом от этого метода до первого значка, значка viewController (также присутствующего в верхней части viewController, в той же строке, что и значок выхода). Выберите "ручной" вариант, который появляется.
-
В схеме документа для одного и того же представления (viewController3) выберите только что созданный раздел размотки. Перейдите к назначенному инспектору и назначьте уникальный идентификатор этого сеанса размотки. Теперь у нас есть общий разворот, готовый к использованию.
-
Теперь разворот можно выполнить так же, как и любой другой код из кода.
performSegueWithIdentifier("unwind.to.myauth", sender: nil)
Этот подход приведет вас из viewController3 в viewController1 без необходимости удаления viewController2 из иерархии навигации.
В отличие от других segues, unind segues не создают экземпляр контроллера представления, они переходят только к существующему контроллеру представления в иерархии навигации.
Ответ 7
Как уже упоминалось в предыдущих ответах, всплывающее окно без анимации, а затем добавление анимации выглядит не очень хорошо, потому что пользователь увидит сам процесс. Я рекомендую сначала нажать анимацию, а затем удалить предыдущий VC. Вот так:
extension UINavigationController {
func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
pushViewController(viewController, animated: animated)
let indexToRemove = viewControllers.count - 2
if indexToRemove >= 0 {
viewControllers.remove(at: indexToRemove)
}
}
}
Ответ 8
Использовать нижестоящий контроллер последнего кода
Вы можете использовать другую кнопку или добавить ее вместо кнопки отмены, которую я использовал
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismiss:)];
self.navigationItemSetting.leftBarButtonItem = cancelButton;
}
- (IBAction)dismissSettings:(id)sender
{
// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];
}
Ответ 9
То, что вы действительно должны сделать, это модально представить UINavigationController
, содержащую социальную сеть UIViewControllers
, верхнюю часть вашего меню UIViewController
(которая может быть встроена в UINavigationController
, если вы хотите). Затем, после аутентификации пользователя, вы отключите социальную сеть UINavigationController
, снова показывая свое меню UIViewController
.
Ответ 10
В swift3
создать один сегмент
идентификатор -add
-add и установить в segue (раскадровки) пользовательский класс раскадровки из файла cocoatouch
-Интерфейс переопределения класса()
override func perform() {
let sourceViewController = self.source
let destinationController = self.destination
let navigationController = sourceViewController.navigationController
// Pop to root view controller (not animated) before pushing
if self.identifier == "your identifier"{
navigationController?.popViewController(animated: false)
navigationController?.pushViewController(destinationController, animated: true)
}
}
-Вы также должны переопределить один метод в вашем исходном представлении управления
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return false
}
Ответ 11
Ну, что вы также можете сделать, это использовать материал диспетчера размотки.
На самом деле я думаю, что это именно то, что вам нужно.
Отметьте эту запись: Что такое Unwind segues и как вы их используете?
Ответ 12
Это работало для меня в Swift 3:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
if let navVC = source.navigationController {
navVC.pushViewController(destination, animated: true)
} else {
super.perform()
}
}
}
Ответ 13
Как насчет этого :) У меня сейчас старый вопрос, но это будет работать как прелесть
UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:@[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;
Ответ 14
Вот быстрое решение Swift 4/5 путем создания пользовательского эффекта, который заменяет (верхний стек) viewcontroller новым (без анимации):
class SegueNavigationReplaceTop: UIStoryboardSegue {
override func perform () {
guard let navigationController = source.navigationController else { return }
navigationController.popViewController(animated: false)
navigationController.pushViewController(destination, animated: false)
}
}