iOS, как определить программно, когда контроллер вид сверху выскочил?
Предположим, у меня есть стек контроллеров навигации с двумя контроллерами представления: VC2 находится сверху, а VC1 - снизу. Есть ли код, который я могу включить в VC1, который обнаружит, что VC2 только что был извлечен из стека?
Поскольку я пытаюсь обнаружить появление VC2 из кода для VC1, кажется, что что-то вроде viewWillAppear или viewDidAppear не будет работать, потому что эти методы срабатывают каждый раз, когда отображается VC1, в том числе когда он впервые помещается в стек.
РЕДАКТИРОВАТЬ: кажется, я не очень ясно с моим первоначальным вопросом. Вот что я пытаюсь сделать: определить, когда показывается VC1 из-за того, что VC2 выскакивает с вершины стека. Вот что я НЕ пытаюсь сделать: определить, когда VC1 показывается из-за того, что он помещен на вершину стека. Мне нужен какой-то способ, который обнаружит первое действие, но НЕ второе действие.
Примечание: Меня не особо волнует VC2, это может быть любое количество других VC, которые извлекаются из стека, и меня волнует, когда VC1 снова становится вершиной стека из-за того, что какой-то другой VC начинает выталкивать из Топ.
10 ответов
iOS 5 представила два новых метода для обработки именно этого типа ситуации. То, что вы ищете, это -[UIViewController isMovingToParentViewController]
, Из документов:
isMovingToParentViewController
Возвращает логическое значение, которое указывает, что контроллер представления находится в процессе добавления к родителю.
- (BOOL)isMovingToParentViewController
Возвращаемое значение
YES, если контроллер представления появляется, потому что он был добавлен как дочерний элемент контроллера представления контейнера, иначе NO.обсуждение
Этот метод возвращает YES только при вызове из следующих методов:
-viewWillAppear:
-viewDidAppear:
В вашем случае вы могли бы реализовать -viewWillAppear:
вот так:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.isMovingToParentViewController == NO)
{
// we're already on the navigation stack
// another controller must have been popped off
}
}
РЕДАКТИРОВАТЬ: Здесь есть небольшая семантическая разница, которую нужно учитывать - вас интересует тот факт, что VC2, в частности, выталкивается из стека, или вы хотите получать уведомления каждый раз, когда VC1 обнаруживается в результате какого- либо срабатывания контроллера? В первом случае делегирование является лучшим решением. Прямая слабая ссылка на VC1 также может сработать, если вы никогда не собираетесь повторно использовать VC2.
РЕДАКТИРОВАТЬ 2: Я сделал пример более явным, перевернув логику и не возвращаясь рано.
isMovingTo/FromParentViewController не будет работать для добавления и извлечения в стек контроллера навигации.
Вот надежный способ сделать это (без использования делегата), но это, вероятно, только iOS 7+.
UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];
if ([[self.navigationController viewControllers] containsObject:fromViewController])
{
//we're being pushed onto the nav controller stack. Make sure to fetch data.
} else {
//Something is being popped and we are being revealed
}
В моем случае использование делегата означало бы, чтобы поведение контроллеров представления было более тесно связано с делегатом, которому принадлежит стек навигации, и я хотел найти более автономное решение. Это работает.
Один из способов добиться этого - объявить протокол делегирования для VC2 примерно так:
в VC1.h
@interface VC1 : UIViewController <VC2Delegate> {
...
}
в VC1.m
-(void)showVC2 {
VC2 *vc2 = [[VC2 alloc] init];
vc2.delegate = self;
[self.navigationController pushViewController:vc2 animated:YES];
}
-(void)VC2DidPop {
// Do whatever in response to VC2 being popped off the nav controller
}
в VC2.h
@protocol VC2Delegate <NSObject>
-(void)VC2DidPop;
@end
@interface VC2 : UIViewController {
id<VC2Delegate> delegate;
}
@property (nonatomic, assign) id delegate;
...
@end
VC2.m
-(void)viewDidUnload {
[super viewDidUnload];
[self.delegate VC2DidPop];
}
Здесь есть хорошая статья об основах протоколов и делегатов.
Вы также можете обнаружить в контроллере представления, который выскочил
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]) {
....
}
}
Это работает для меня
UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];
if (![[self.navigationController viewControllers] containsObject:fromViewController] && !self.presentedViewController)
{
//Something is being popped and we are being revealed
}
У меня такая же ситуация, но с более конкретным случаем использования. В моем случае мы хотели определить, появляется ли / отображается ли VC1, когда пользователь нажимает на кнопку "Назад" VC2, где VC2 нажимается на navigationController поверх VC1.
Так что я воспользовался помощью ответа snarshad на заказ согласно моей потребности. Вот код в VC1 viewDidAppear
в кратчайшие сроки 3.
// VC1: ParentViewController
// VC2: ChildViewController
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let transitionCoordinator = navigationController?.transitionCoordinator,
let fromVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.to),
fromVC is ChildViewController,
toVC is ParentViewController {
print("Back button pressed on ChildViewController, and as a result ParentViewController appeared")
}
}
Свифт 3
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.isMovingToParentViewController {
print("View is moving to ParentViewControll")
}
}
Что вы конкретно пытаетесь сделать?
Если вы пытаетесь определить, что VC1 скоро появится, этот ответ должен вам помочь. Используйте UINavigationControllerDelegate.
Если вы пытаетесь обнаружить, что VC2 собирается быть скрытым, я бы просто использовал viewWillDisappear:
VC2.
Вы можете добавить наблюдателя для NSNotification специально для вашего VC2.
// pasing the "VC2" here will tell the notification to only listen for notification from
// VC2 rather than every single other objects
[[NSNotitificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) object:VC2];
Теперь в вашем VC2 вид исчезнет, вы можете опубликовать уведомление:
-(void)viewWillDisappear
{
[[NSNotificationCenter defaultCenter] postNotificationNamed:@"notif_dismissingVC2" object:nil];
}
Да, в VC1 вы можете проверить, работает ли VC2 или нет. UINavigationController есть один метод viewControllers, который возвращает массив помещенных контроллеров, которые находятся в стеке (т.е. которые были помещены).
Таким образом, вы повторяете цикл, сравнивая класс. Если VC2 есть, будет совпадать, иначе нет.