it-roy-ru.com

Модальные контроллеры View - как отображать и отклонять

Последние недели я ломаю голову над тем, как решить проблему с показом и удалением нескольких контроллеров представления. Я создал пример проекта и вставил код прямо из проекта. У меня есть 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. Там нет долго работающего кода, циклов или каких-либо таймеров. Просто голые звонки для просмотра контроллеров.

80
Hema

Эта строка:

[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 над @interface:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

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

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

В файле VC1 .m метод кнопки 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: может быть тем же методом, что и ваш метод VC2Pressed: кнопки IBAction. Обратите внимание, что он вызывается из блока завершения, чтобы гарантировать, что VC2 не представлен, пока VC1 полностью не отклонен.

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

обновление

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

В Apple View Controller Руководство по программированию они имеют это сказать :

Отклонение Контроллера Представленного Представления

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

Если вы действительно продумываете, чего вы хотите достичь, и как вы собираетесь это сделать, вы поймете, что обмен сообщениями вашего MainViewController для выполнения всей работы является единственным логическим выходом, если вы не хотите использовать NavigationController. Если вы действительно используете 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 отклонен, он не может выполнить блок завершения ... или это так? Значит ли что-нибудь еще self.presentingViewController? Вы не знаете (и я тоже) ... с делегатом, у вас нет этой неопределенности.
  • Когда я пытаюсь запустить этот метод, он просто зависает без предупреждения или ошибок.

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

Update2

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

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

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

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

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

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

  • свойство mainVC presentedViewController
  • Свойство VC2 presentingViewController

Вы можете проверить это, войдя в систему, а также просто сделав это из 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 , из которых VC2 является представленным VC. Второе сообщение на самом деле не выполняется - возможно, что-то происходит, но у вас все еще остаются два указателя на объект, от которого, как вы думали, вы избавились. ( edit - я проверил это, и это не так уж плохо, оба объекта исчезают, когда вы возвращаетесь в mainVC )

Это довольно многословный способ сказать - пожалуйста, используйте делегатов. Если это поможет, я сделал еще одно краткое описание шаблона здесь:
Передача контроллера в строителе - это всегда плохая практика?

обновление 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, но это лучшее, что вы получите без явного делегирования.

186
foundry

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

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

ViewController.Swift

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.Swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

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

}

ViewController2.Swift

import UIKit

class ViewController2: UIViewController {

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

ViewController.Swift

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.Swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

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

}

ViewController2.Swift

import UIKit

class ViewController2: UIViewController {

}
9
King-Wizard

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

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

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

9
Radu Simionescu

Раду Симионеску - потрясающая работа! и ниже Ваше решение для любителей 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)
    })
}
4
chrisco

Я хотел это:

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

после @interface новое свойство

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

PopupVC.m:

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

Я решил проблему с помощью 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)
0
Duong Ngo