модальные контроллеры вида - как отобразить и отклонить


я ломаю голову в течение последней недели о том, как решить проблему с показом и отклонением нескольких контроллеров вида. Я создал пример проекта и вставил код непосредственно из проекта. У меня есть 3 вид контроллеров с соответствующими .xib файлов. MainViewController, VC1 и VC2. У меня есть две кнопки на главном контроллере вида.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

это открывает VC1 без проблем. В VC1 у меня есть еще одна кнопка, которая должна открыть VC2 в то же время VC1 и уволить.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

Я хочу, чтобы он вернулся к контроллеру основного вида, в то время как в то же время VC1 должен был быть удален из памяти навсегда. VC1 должен отображаться только тогда, когда я нажимаю на кнопку VC1 на главном контроллере.

другая кнопка на контроллере основного вида также должна иметь возможность отображать VC2 непосредственно в обход VC1 и должна возвращаться к основному контроллеру при нажатии кнопки на VC2. Там нет длинного кода, циклов или каких-либо таймеров. Просто голый кости звонков для просмотра контроллеров.

6 78

6 ответов:

эта строка:

[self dismissViewControllerAnimated:YES completion:nil];

не отправляет сообщение самому себе, он фактически отправляет сообщение своему представляющему VC, прося его сделать увольнение. Когда вы представляете VC, вы создаете связь между представляющим VC и представленным. Поэтому вы не должны уничтожать представляющий VC во время его представления (представленный VC не может отправить это сообщение об увольнении обратно...). Поскольку вы на самом деле не учитываете это, вы оставляете приложение в запутанном состоянии. Смотрите мой ответ отклонение представленного контроллера вида в которой я рекомендую этот метод более четко написано:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

в вашем случае, вы должны убедиться, что все управление в mainVC. Вы должны использовать делегат для отправки правильного сообщения обратно в MainViewController из ViewController1, чтобы mainVC мог отклонить VC1, а затем представить VC2.

на VC2 VC1 добавить протокол в вашем .H-файл выше @интерфейс:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

и ниже в том же файле в разделе @interface объявите свойство для хранения указателя делегата:

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

в VC1 .m file, метод кнопки dismiss должен вызывать метод делегата

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

сейчас в mainVC, установить его в качестве делегата VC1 и при создании VC1 и:

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}

и реализовать метод делегата:

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}

present2: может быть тот же метод, что и ваш Метод IBAction. Обратите внимание, что он вызывается из блока завершения, чтобы гарантировать, что VC2 не представлен, пока VC1 не будет полностью отклонен.

теперь вы переходите от VC1->VCMain - >VC2, поэтому вы, вероятно, захотите, чтобы только один из переходов был анимирован.

обновление

в своих комментариях вы выражаете удивление сложностью, необходимой для достижения, казалось бы, простой вещи. Я уверяю вас, что эта модель делегирования настолько важна для многих Цель-C и какао, и этот пример о самом простом, что вы можете получить, что вы действительно должны приложить усилия, чтобы освоиться с ним.

в Apple Руководство По Программированию Контроллера Вида они сказал:

отклонение представленного контроллера вида

когда приходит время уволить представленный контроллер вида, предпочтительный подход заключается в том, чтобы позволить представить контроллер вида уволить его. Другими словами, всякий раз, когда это возможно, тот же контроллер вида, который представил контроллер вида, также должен взять на себя ответственность за его отклонение. Хотя существует несколько способов уведомления контроллера представления о том, что его контроллер представления должен быть отклонен, предпочтительным способом является делегирование. Дополнительные сведения см. В разделе " использование делегирования для связи с другими контроллерами."

если вы действительно думаете, что вы хотите достичь, и как вы собираетесь об этом, вы поймете, что сообщение вашего MainViewController для выполнения всей работы является единственным логическим выходом, учитывая, что вы не хотите использовать NavigationController. Если вы do используйте NavController, фактически вы "делегируете", даже если не явно, navController, чтобы выполнить всю работу. Там должно быть некоторые объект, который держит центральный след того, что происходит с вашей навигацией VC, и вам нужно некоторые способ общения с ним, что бы вы ни делали.

на практике советы Apple немного экстремальны... в обычных случаях вам не нужно создавать выделенный делегат и метод, вы можете положиться на [self presentingViewController] dismissViewControllerAnimated: - это когда в случаях, подобных вашему, вы хотите, чтобы ваше увольнение имело другие эффекты на удаленные объекты, которые вам нужно позаботиться.

вот то, что вы могли бы представьте себе, работать без всех хлопот делегата...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

после того, как попросили представляя контроллер, чтобы уволить нас, у нас есть блок завершения, который вызывает метод в presentingViewController для вызова VC2. Делегат не нужен. (Большой пункт продажи блоков заключается в том, что они уменьшают потребность в делегатах в этих условиях). Однако в этом случае есть несколько вещей, которые мешают...

  • в VC1 вы не знаю что mainVC реализует метод present2 - вы можете в конечном итоге с трудным для отладки ошибок или сбоев. Делегаты помогут вам избежать этого.
  • после того, как VC1 уволен, это не совсем вокруг, чтобы выполнить блок завершения... или это так? Делает сам.presentingViewController означает что-нибудь еще? Вы не знаете (как и я)... с делегатом, у вас нет этой неопределенности.
  • когда я пытаюсь запустить этот метод, он просто зависает без каких-либо предупреждений или ошибок.

так что, пожалуйста... потратьте время, чтобы узнать делегация!

обновление 2

в вашем комментарии Вам удалось заставить его работать, используя это в обработчике кнопки увольнения VC2:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

это, конечно, гораздо проще, но это оставляет вас с рядом вопросов.

жесткая связь
Вы жестко соединяете свою структуру viewController вместе. Например, если вы должны были вставить новый viewController перед mainVC, ваше требуемое поведение будет перерыв (вы перейдете к предыдущему). В VC1 вам также пришлось #импортировать VC2. Поэтому у вас довольно много взаимозависимостей, что нарушает цели ООП/MVC.

используя делегатов, ни VC1, ни VC2 не должны ничего знать о mainVC или его антецедентах, поэтому мы сохраняем все слабо связанные и модульные.


VC1 не ушел, вы все еще держите два указателя на него:

  • mainVC presentedViewController собственность
  • вк2 это!--19--> свойства

вы можете проверить это путем регистрации, а также просто сделав это из VC2

[self dismissViewControllerAnimated:YES completion:nil]; 

он все еще работает, все еще возвращает вас к VC1.

мне это кажется утечкой памяти.

ключ к этому находится в предупреждении, которое вы получаете здесь:

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

логика ломается, как вы пытаетесь уволить представляя VC из которых вк2 является представленный ВК. Второе сообщение на самом деле не выполняется - ну, возможно, некоторые вещи происходят, но у вас все еще остаются два указателя на объект, от которого вы думали, что избавились. (edit-я проверил это, и это не так уж плохо, оба объекта уходят, когда вы возвращаетесь в mainVC)

проходит a контроллер в construtor всегда плохая практика?

обновление 3
Если вы действительно хотите избежать делегатов, это может быть лучшим выходом:

в VC1:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

но не что-нибудь уволить... как мы выяснили, на самом деле этого все равно не происходит.

в VC2:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

как мы (знаем) мы не уволили VC1, мы можем вернуться через VC1 до MainVC. MainVC увольняет VC1. Поскольку VC1 ушел, он представлен VC2 идет с ним, поэтому вы вернулись в MainVC в чистом состоянии.

он все еще сильно связан, так как VC1 должен знать о VC2, и VC2 должен знать, что он был получен через MainVC->VC1, но это лучшее, что вы получите без немного явного делегирования.

Я думаю, что вы неправильно поняли некоторые основные понятия о модальных контроллерах iOS. Когда вы отклоняете VC1, любые представленные контроллеры представления VC1 также отклоняются. Apple, предназначенная для модальных контроллеров представления, чтобы течь в сложенном виде - в вашем случае VC2 представлен VC1. Вы увольняете VC1, как только вы представляете VC2 из VC1, так что это полный беспорядок. Для достижения того, что вы хотите, buttonPressedFromVC1 должен иметь mainVC присутствует VC2 сразу после того, как VC1 увольняет себя. И я думаю, что этого можно добиться и без делегатов. Что-то вроде:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

обратите внимание на себя.presentingViewController хранится в какой-то другой переменной, потому что после того, как vc1 отклоняет себя, вы не должны делать никаких ссылок на него.

пример Свифт, изображая объяснение литейного завода выше и документацию Apple:

  1. на основе документация Apple и литейный завод объяснение выше (исправление некоторых ошибок), presentViewController версия с использованием делегата дизайн выкройка:

ViewController.Свифт

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.Свифт

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.Свифт

import UIKit

class ViewController2: UIViewController {

}
  1. основываясь на объяснении литейного завода выше (исправление некоторых ошибок), версия pushViewController с использованием делегата design выкройка:

ViewController.Свифт

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.Свифт

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.Свифт

import UIKit

class ViewController2: UIViewController {

}

Раду Симионеску - потрясающая работа! и ниже ваше решение для любителей Swift:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}

Я хотел этого:

MapVC-это карта в полноэкранном режиме.

когда я нажимаю кнопку, она открывает PopupVC (не в полноэкранном режиме) над картой.

когда я нажимаю кнопку в PopupVC, он возвращается к MapVC, а затем я хочу выполнить viewDidAppear.

Я сделал так:

MapVC.m: в действии кнопки, сегментируйте программно и установите делегат

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h: перед @interface, добавьте протокол

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

после @интерфейс, новое свойство

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.м:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}

я решил эту проблему с помощью UINavigationController при представлении. В MainVC, при представлении VC1

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

В VC1, когда я хотел бы показать VC2 и уволить VC1 в то же время (только одна анимация), я могу иметь push-анимацию

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

и в VC2, когда закрыть контроллер вида, как обычно, мы можем использовать:

self.dismiss(animated: true, completion: nil)