제시된 뷰 컨트롤러가 닫힐 때 감지


82

VC2라는 뷰 컨트롤러 클래스의 인스턴스가 있다고 가정 해 보겠습니다. VC2에는 자동으로 해제되는 "취소"버튼이 있습니다. 하지만 "취소"버튼이 트리거되면 콜백을 감지하거나 수신 할 수 없습니다. VC2는 블랙 박스입니다.

뷰 컨트롤러 (VC1이라고 함)는 presentViewController:animated:completion:메서드를 사용하여 VC2를 표시 합니다.

VC2가 해제되었을 때 VC1이 감지해야하는 옵션은 무엇입니까?

편집 : @rory mckinnel의 의견과 @NicolasMiari의 답변에서 다음을 시도했습니다.

VC2에서 :

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

VC1에서 :

//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
                           completion:(void (^ _Nullable)(void))completion
{
    NSLog(@"%s ", __PRETTY_FUNCTION__);
    [super dismissViewControllerAnimated:flag completion:completion];
//    [self dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

그러나 dismissViewControllerAnimatedVC1에서 호출되지 않았습니다.


2
VC1에서 viewWillAppear 메서드가 호출됩니다
Istvan

1
문서에 따르면 발표하는 컨트롤러는 실제 해제를 담당합니다. 제시된 컨트롤러가 스스로 닫히면 발표자에게이를 수행하도록 요청합니다. 따라서 dismissViewControllerAnimatedVC1 컨트롤러에서 재정의 하면 VC2에서 취소를 누를 때 호출 될 것이라고 생각합니다. 해제를 감지 한 다음 실제 해제를 수행 할 슈퍼 클래스 버전을 호출합니다.
Rory McKinnel 2015 년

1
을 호출하여 재정의를 테스트 할 수 있습니다 [self.presentingViewController dismissViewControllerAnimated]. 내부 코드에는 발표자에게 해제를 요청하는 메커니즘이 다를 수 있습니다.
Rory McKinnel 2015 년

@RoryMcKinnel : self.presentingViewController를 사용하면 실제 블랙 박스뿐만 아니라 랩 VC2에서도 작동했습니다. 답변에 의견을 넣으면 답변으로 선택하겠습니다. 감사.
user523234 2015 년

이에 대한 해결책은 다음 관련 게시물에서 찾을 수 있습니다. stackoverflow.com/a/34571641/3643020
Campbell_Souped

답변:


64

문서에 따르면 발표하는 컨트롤러는 실제 해제를 담당합니다. 제시된 컨트롤러가 스스로 닫히면 발표자에게이를 수행하도록 요청합니다. 따라서 VC1 컨트롤러에서 dismissViewControllerAnimated를 재정의하면 VC2에서 취소를 누를 때 호출 될 것이라고 믿습니다. 해제를 감지 한 다음 실제 해제를 수행 할 슈퍼 클래스 버전을 호출합니다.

토론에서 알 수 있듯이 이것은 작동하지 않는 것 같습니다. 오히려 대신 호출하는 기본 메커니즘에 의존하는 것보다 dismissViewControllerAnimated:completionVC2 자체 통화 dismissViewControllerAnimated:completionself.presentingViewControllerVC2한다. 그러면 재정의가 직접 호출됩니다.

더 나은 접근 방식은 VC2가 모달 컨트롤러가 완료 될 때 호출되는 블록을 제공하도록하는 것입니다.

따라서 VC2에서 이름과 함께 블록 속성을 제공하십시오 onDoneBlock.

VC1에서는 다음과 같이 발표합니다.

  • VC1에서 VC2를 만듭니다.

  • VC2에 대한 완료 핸들러를 다음과 같이 설정하십시오. VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • [self presentViewController : VC2 animated : YES 완료 : nil]을 사용하여 VC2 컨트롤러를 정상적으로 표시합니다.

  • VC2에서 취소 대상 작업 호출에서 self.onDoneBlock();

결과는 VC2가 누구에게나 그것이 완료되었음을 알려줍니다. onDoneBlock모달이 완료되었는지, 취소되었는지, 성공했는지 등을 나타내는 인수를 갖도록를 확장 할 수 있습니다 .


2
4 년이 지난 후에도 얼마나 아름답게 작동하는지 감사하고 감사합니다! 감사합니다!
Anna

48

이 목적으로 사용할 수있는 UIViewController라는 특수 부울 속성 isBeingDismissed이 있습니다.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // TODO: Do your stuff here.
    }
}

3
가장 쉬운 베스트 답변은 대부분의 문제를 올바르게 해결하며 추가 구현이 필요하지 않습니다.
reojased

와 페어링하지 않으면 제대로 작동하지 않습니다 viewDidAppear.
Dmitry

9
iOS13 모달 프레젠테이션에서는 사용자가 컨트롤러를 드래그하여 닫기를 시작할 때 적용되지만 닫기를 완료하지 않도록 선택할 수 있습니다.
Estel

44

블록 속성 사용

VC2에서 선언

var onDoneBlock : ((Bool) -> Void)?

VC1에서 설정

VC2.onDoneBlock = { result in
                // Do something
            }

해고하려고 할 때 VC2를 호출하십시오.

onDoneBlock!(true)

@ Bryce64 그것은 나를 위해 작동하지 않습니다, 나는 "Thread 1 : Fatal error : Unexpectedly found nil while unwrapping an Optional value"라는 코드가 onDoneBlock! (true)로 이동하는 지점에서 발생했습니다.
Lucas

@Lucas VC1에서 올바르게 선언하지 않은 것 같습니다. "!" 올바르게 설정하지 않은 경우 언 래핑이 강제로 오류를 발생시킵니다.
brycejl

1
하나의 뷰 컨트롤러 만 제공된다고 가정합니다. 탐색 스택에있을 수 있습니다.
Lee Probert

@LeeProbert 정확합니다. 스택 옆에 약 10 개의 가능한 하위 컨트롤러가 포함 된 내비게이션 컨트롤러가 있으며, 거의 모든 컨트롤러가 해제를 트리거 할 수 있습니다.이 상황에서 모든 완료 블록은 해당 컨트롤러 10 개 모두에 전달되어야합니다
Igor Vasilev

13

프레젠테이션 프레젠테이션보기 컨트롤러는 모두 프레젠테이션보기 컨트롤러 dismissViewController:animated:를 해제하기 위해 호출 할 수 있습니다 .

전자의 옵션은 디자인 적으로 "올바른"옵션입니다. 동일한 "부모"뷰 컨트롤러가 모달 ( "하위") 뷰 컨트롤러를 표시하고 해제하는 역할을합니다.

그러나 후자가 더 편리합니다. 일반적으로 "닫기"버튼이 제시된 뷰 컨트롤러의 뷰에 연결되고 뷰 컨트롤러가 작업 대상으로 설정되어 있습니다.

이전 접근 방식을 채택하는 경우, 해제가 발생하는 프레젠테이션 뷰 컨트롤러의 코드 줄을 이미 알고 있습니다. 바로 뒤에 코드를 실행 dismissViewControllerAnimated:completion:하거나 완료 블록 내에서 코드를 실행합니다 .

후자의 접근 방식을 채택하는 경우 (제공된 뷰 컨트롤러가 자동으로 해제 됨), dismissViewControllerAnimated:completion:제시된 뷰 컨트롤러에서 호출 하면 UIKit이 제시하는 뷰 컨트롤러에서 해당 메서드를 차례로 호출하게됩니다.

토론

프리젠 테이션 뷰 컨트롤러는 자신이 제시 한 뷰 컨트롤러를 해제 할 책임이 있습니다. 제시된 뷰 컨트롤러 자체에서이 메서드를 호출하면 UIKit은 제시 뷰 컨트롤러에게 해제를 처리하도록 요청합니다.

( 출처 : UIViewController Class Reference )

따라서 이러한 이벤트를 가로 채기 위해 프레젠테이션 뷰 컨트롤러 에서 해당 메서드를 재정의 할 수 있습니다 .

override func dismiss(animated flag: Bool,
                         completion: (() -> Void)?) {
    super.dismiss(animated: flag, completion: completion)

    // Your custom code here...
}

1
문제 없어요. 그러나 예상대로 작동하지 않는 것으로 나타났습니다. 고맙게도 @RoryMcKinnel의 답변은 더 많은 옵션을 제공하는 것 같습니다.
Nicolas Miari 2015 년

이 접근 방식은 dismissViewControllerAnimated를 재정의하는 기본 뷰 컨트롤러 광고에서 뷰 컨트롤러를 하위 클래스 화하기에 충분히 일반적이지만. 당신이 탐색 뷰 컨트롤러에서보기 CONTROLER에서 마무리하려고하면 그러나 실패
hariszaman

4
사용자가 상단에서 스 와이프하여 모달 뷰 컨트롤러를 닫을 때 호출되지 않습니다!
Dmitry

3
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo

1
iOS 13.0+전용
gondo 20.04.24

2

unwind segue를 사용하여이 작업을 수행 할 수 있으며 dismissModalViewController를 사용할 필요가 없습니다. VC1에서 unwind segue 방법을 정의하십시오.

unwind segue를 만드는 방법에 대한이 링크, https://stackoverflow.com/a/15839298/5647055를 참조하십시오 .

unwind segue가 설정되었다고 가정하면 "Cancel"버튼에 대해 정의 된 작업 방법에서 다음과 같이 segue를 수행 할 수 있습니다.

[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];

이제 VC2에서 "취소"버튼을 누를 때마다 해제되고 VC1이 나타납니다. 또한 VC1에서 정의한 unwind 메서드를 호출합니다. 이제 제시된 뷰 컨트롤러가 언제 닫혔는지 알 수 있습니다.


2

다음을 사용하여 코디네이터에게 뷰 컨트롤러가 "완료"되었음을 알립니다. 이것은 AVPlayerViewControllertvOS 애플리케이션 의 하위 클래스에서 사용되며 playerVC 해제 전환이 완료된 후에 호출됩니다.

class PlayerViewController: AVPlayerViewController {
  var onDismissal: (() -> Void)?

  override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) {
    super.beginAppearanceTransition(isAppearing, animated: animated)
    transitionCoordinator?.animate(alongsideTransition: nil,
      completion: { [weak self] _ in
         if !isAppearing {
            self?.onDismissal?()
        }
    })
  }
}

AVPLayerViewController에서 상속하면 안됩니다. Apple 문서에 따르면 "AVPlayerViewController를 서브 클래 싱하고 해당 메서드를 재정의하는 것은 지원되지 않으며 정의되지 않은 동작이 발생합니다."
Neru

2

을 사용하여 willMove(toParent: UIViewController?)다음과 같은 방법으로 나를 위해 일 듯. (iOS12에서 테스트 됨).

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}

1

@ user523234- "하지만 VC1의 dismissViewControllerAnimated가 호출되지 않았습니다."

VC1이 실제로 프레젠테이션을 수행한다고 가정 할 수 없습니다. 루트 뷰 컨트롤러 인 VC0이 될 수 있습니다. 관련된 3 개의 뷰 컨트롤러가 있습니다.

  • sourceViewController
  • presentingViewController
  • presentViewController

당신의 예에서, VC1 = sourceViewController, VC2 = presentedViewController, ?? = presentingViewController- 어쩌면 VC1, 아마.

그러나 VC2를 닫을 때 항상 VC1.animationControllerForDismissedController를 호출 할 수 있으며 (대리자 메서드를 구현 한 경우) 해당 메서드에서 VC1로 원하는 작업을 수행 할 수 있습니다.


1

나는이 문제를 다룰 때이 게시물을 너무 많이 보았고 마침내 가능한 대답에 대해 밝힐 것이라고 생각했습니다.

사용자가 시작한 작업 (예 : 화면의 제스처)이 UIActionController에 대해 해제되었는지 여부를 알고 하위 클래스 나 확장을 만드는 데 시간을 투자하고 싶지 않은 경우 대안이 있습니다.

그것이 나오는 것에 따라, popoverPresentationController의 의 특성 UIActionController (그 효과가 아니라, 어떤의 UIViewController 또는)하는이 대표 는 유형 인 코드에 언제든지 설정할 수 있습니다 UIPopoverPresentationControllerDelegate을 , 다음과 같은 방법이 있습니다 :

액션 컨트롤러에서 델리게이트를 할당하고, 델리게이트 클래스 (뷰, 뷰 컨트롤러 등)에서 선택한 메소드를 구현하고 짜잔!

도움이 되었기를 바랍니다.


그리고 그것들은 iOS 13부터 더 이상 사용되지 않습니다. Doh
pronebird

1

또 다른 옵션은 사용자 정의 UIPresentationController의 dismissalTransitionDidEnd ()를 수신하는 것입니다.


0
  1. 하나의 클래스 파일 (.h / .m)을 만들고 이름을 DismissSegue로 지정합니다.
  2. 선택 하위 클래스 : UIStoryboardSegue

  3. DismissSegue.m 파일로 이동하여 다음 코드를 작성합니다.

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. 스토리 보드를 열고 취소 버튼에서 VC1로 Ctrl + 드래그하고 해제로 Action Segue를 선택하면 완료됩니다.


0

사라지는 뷰 컨트롤러에서 재정의하는 경우 :

override func removeFromParentViewController() {
    super.removeFromParentViewController()
    // your code here
}

적어도 이것은 나를 위해 일했습니다.


@JohnScalo는 사실이 아닙니다. "네이티브"뷰 컨트롤러 계층 구조 중 상당수가 자식 / 부모 프리미티브로 스스로를 구현합니다.
mxcl


0

overrideing viewDidAppear이 나를 위해 트릭을했습니다. 내 모달에서 싱글 톤 을 사용했고 이제 호출 VC, 모달 및 기타 모든 곳에서 설정하고 가져올 수 있습니다.


viewDidAppear?
Dmitry

1
viewDidDisappear를 의미 했습니까?
Dale

0

viewWillDisappear제시된 뷰 컨트롤러의 오버라이드 기능.

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}

와 페어링하지 않으면 제대로 작동하지 않습니다 viewDidAppear.
Dmitry

1
super.viewWillDisappear
Dale

0

앞서 언급했듯이 해결책은 override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil).

override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)항상 작동하지 않는 것처럼 보이는지 궁금한 사람들 은 통화가 UINavigationController관리되고 있는 경우 a에 의해 차단 되고 있음을 알 수 있습니다. 도움이 될 하위 클래스를 작성했습니다.

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }


0

뷰 컨트롤러 해제를 처리하려면 아래 코드를 사용해야합니다.

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (self.isBeingDismissed && self.completion != NULL) {
        self.completion();
    }
}

불행히도 재정의 된 메서드에서는 완료를 호출 할 수 없습니다. (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;이 메서드는이 뷰 컨트롤러의 dismiss 메서드를 호출하는 경우에만 호출 되었기 때문입니다.


하지만 viewWillDisappear.NET과 페어링하지 않으면 제대로 작동하지 않습니다 viewDidAppear.
Dmitry

viewWillDisappear는 VC가 완전히 덮일 때 호출됩니다 (예 : 모달 사용). 당신은 해고되지 않았을 수 있습니다
루 프랑코을

0

ViewController에 대해 deinit 를 사용 했습니다.

deinit {
    dataSource.stopUpdates()
}

deinitializer는 클래스 인스턴스가 할당 해제되기 직전에 호출됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.