iOS 13에서 시트 감지가 닫혔습니다.


120

iOS 13 이전에는 전체 화면을 덮는 데 사용되는 뷰 컨트롤러가 제시되었습니다. 그리고 해제되면 상위 뷰 컨트롤러 viewDidAppear기능이 실행되었습니다.

이제 iOS 13은 뷰 컨트롤러를 기본으로 시트로 표시합니다. 즉, 카드가 기본 뷰 컨트롤러를 부분적으로 덮을 것입니다. 즉 viewDidAppear, 부모 뷰 컨트롤러가 실제로 사라지지 않았기 때문에 호출되지 않습니다.

제시된 뷰 컨트롤러 시트가 닫 혔음감지 하는 방법이 있습니까? 어떤 종류의 델리게이트를 사용 하는 대신 부모 뷰 컨트롤러에서 재정의 할 수있는 다른 기능이 있습니까?



그렇다면 한 번에 모든 모달 시트를 루트 vc에 해제하는 방법이 있습니까?
Weslie


언제 해산되었는지 알아야하는 이유는 무엇입니까? 데이터를 다시로드하고 UI를 업데이트하려면 알림 또는 KVO가 좋은 대안이 될 수 있습니다.
martn_st

답변:


61

제시된 뷰 컨트롤러 시트가 닫 혔음을 감지하는 방법이 있습니까?

예.

어떤 종류의 델리게이트를 사용하는 대신 부모 뷰 컨트롤러에서 재정의 할 수있는 다른 기능이 있습니까?

아니요. "어떤 종류의 대리인"이 당신이하는 방식입니다. 자신을 프레젠테이션 컨트롤러의 델리게이트로 만들고 presentationControllerDidDismiss(_:).

https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss


전체 화면이든 아니든, 제시된 뷰 컨트롤러가 닫 혔음을 알리는 일반적인 런타임 생성 이벤트의 부족은 실제로 문제가됩니다. 그러나 항상 전체 화면이 아닌 뷰 컨트롤러가 있기 때문에 새로운 문제는 아닙니다. 이제 (iOS 13에서) 더 많은 것들이 있습니다! 다른 곳에서이 주제에 대해 별도의 질문과 대답을 할애합니다. Unified UIViewController가 "최전방이 된"탐지입니까? .


6
충분하지 않다. 제시된 VC에 nabber가 있고 프로그래밍 방식으로 뷰를 닫는 사용자 지정 막대 단추가있는 경우 프레젠테이션 컨트롤러가 닫힘이 호출되지 않습니다.
Irina

15
안녕 @Irina은 - 프로그래밍보기를 기각하는 경우 프로그래밍보기를 기각하기 때문에, 당신은 콜백이 필요하지 않습니다 - 당신이 알고 있기 때문에 당신이 그것을했다 당신이 그것을했다. 위임 메서드는 사용자 가 수행하는 경우에만 사용 됩니다.
matt

6
@matt 답변 해 주셔서 감사합니다. 뷰가 프로그래밍 방식으로 닫힐 때 이것은 호출되지 않으며 (Irina가 말했듯이) 우리가 그랬다는 것을 알고있는 것이 맞습니다. iOS13의 새로운 모달 프레젠테이션 스타일로 일종의 'viewWillAppear'를 얻기 위해 작성할 불필요한 양의 상용구 코드가 있다고 생각합니다. 그것은 특히 당신이 추출 라우팅이 아키텍처를 통해 라우팅 관리 할 때 지저분한 (MVVM + 코디네이터에, 또는 예를 들어 VIPER의 라우터 타입) 도착
아담 웨이트에게

3
@AdamWaite 동의하지만이 문제는 새로운 것이 아닙니다. 팝 오버, 전체 화면이 아닌 뷰 컨트롤러, 경고 등으로 수년 동안이 문제가 발생했습니다. 나는 이것이 애플의 "이벤트"레퍼토리의 심각한 결함이라고 생각한다. 나는 단지 현실이 무엇이며 왜 그런지 말하고 있습니다. 나는 여기서 문제와 직접 씨름한다 : stackoverflow.com/questions/54602662/…
matt

1
presentationControllerDidDismiss (_ :). Child VC에서 뒤로 버튼을 클릭해도 호출되지 않습니다. 도움이 되셨습니까?
Krishna Meena

42

다음 은 시트로 표시되는 자식 뷰 컨트롤러 (즉, 기본 iOS 13 방식)가 닫힐 때 알림을받는 부모 뷰 컨트롤러의 코드 예제입니다 .

public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
{
  // This is assuming that the segue is a storyboard segue; 
  // if you're manually presenting, just see the delegate there.
  public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
  {
    if segue.identifier == "mySegue" {
      segue.destination.presentationController?.delegate = self;
    }
  }

  public func presentationControllerDidDismiss(
    _ presentationController: UIPresentationController)
  {
    // Only called when the sheet is dismissed by DRAGGING.
    // You'll need something extra if you call .dismiss() on the child.
    // (I found that overriding dismiss in the child and calling
    // presentationController.delegate?.presentationControllerDidDismiss
    // works well).
  }
}

Jerland2의 대답은 시트가 될 때 (A) 원래 질문자가 함수 호출을받을 싶어하기 때문에, 혼란 기각 (그가 전화했을 때 사용자가 시도되고 presentationControllerDidAttemptToDismiss 구현 반면 과 실패 , 그리고 (b) 설정 isModalInPresentation 시트를 닫를) 완전히 직교하며 실제로 제시된 시트를 닫을 수 없게 만듭니다 (OP가 원하는 것과 반대 임).


6
이것은 잘 작동합니다. 호출 된 VC에서 내비게이션 컨트롤러를 사용하는 경우 내비게이션 컨트롤러를 presentationController?, delegate (내비게이션에 topViewController로있는 VC가 아님)로 할당해야한다는 팁입니다.
instAustralia

@instAustralia 이유를 설명하거나 문서를 참조 할 수 있습니까? 감사.
Ahmed Osama

presentationControllerDidDismiss 사용자가 뒤로 버튼을 누를 때 호출되는 방법은 무엇입니까?
Krishna Meena

@AhmedOsama-내비게이션 컨트롤러는 프레젠테이션 컨트롤러이므로 해고에 응답하는 대리인이됩니다. Nav 컨트롤러에 포함 된 VC도 시도했지만 여기에서 해제 할 실제 버튼이 존재하고 응답합니다. 나는 애플의 문서에서 직접 찾을 수 있지만 여기 참조 sarunw.com/posts/modality-changes-in-ios13
instAustralia

26

또 다른 옵션은 다시 얻을 수 viewWillAppearviewDidAppear세트입니다

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen

이 옵션은 전체 화면을 덮고 닫은 후 위의 메서드를 호출합니다.


2
PiterPan 감사합니다. 이것은 효과가 있습니다. 이것은 훌륭하고 빠른 해결입니다.
Erkam KUCET

이전 기본 동작을 복원하는 빠르고 안정적인 방법에 감사드립니다. 이 수정 사항을 즉시 적용한 다음 합리적인 방식으로 새로운 동작으로의 전환을 계획 할 수 있다는 점이 좋습니다.
Ian Lovejoy

14
이것은 수정이 아닌 해결 방법입니다. 모든 사람이 iOS 12 스타일 시트로 되 돌리는 것은 좋지 않습니다. iOS 13은 멋지다! :)
Matt

1
iPad에서는 모달로 표시 될 때 iPad가 기본적으로 페이지 시트로 표시되므로주의해서 사용하세요. 이렇게하면 iPad가 전체 화면으로 표시됩니다
wyu

나를 위해 일하지 않습니다. 모달 컨트롤러를 엽니 다. 닫기로 닫지 만 willAppear는 호출되지 않았습니다. 왜? 감사합니다
neo999

20

미래의 독자를 위해 여기에 구현에 대한 더 완전한 답변이 있습니다.

  1. 루트 뷰 컨트롤러에서 segue를 준비하려면 다음을 추가하십시오 (모달에 nav 컨트롤러가 있다고 가정)
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. 모달 뷰 컨트롤러에서 다음 대리자 + 메서드를 추가합니다.
// MARK: - iOS 13 Modal (Swipe to Dismiss)

extension ModalViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {


        print("slide to dismiss stopped")
        self.dismiss(animated: true, completion: nil)
    }
}
  1. 모달 뷰 컨트롤러에서 대리자 메서드를 호출하려면 다음 속성이 참인지 확인하십시오.
    self.isModalInPresentation = true
  1. 이익

1
self.isModalInPresentation = true이면 드래그 해제가 작동하지 않습니다. 그 줄 대리자 메서드를 제거하는 것은 여전히 ​​괜찮습니다. 감사합니다.
Yogesh Patel

2
이것은 (a) 원래 질문자가 시트가 닫힐 때 함수 호출을 받기를 원했기 때문에 (사용자가 시트를 닫으려고 시도하고 실패 할 때 호출되는 presentationControllerDidAttemptToDismiss를 구현했습니다) (b) 설정 isModalInPresentation 완전히 직교하며 실제로 제시된 시트를 닫을 수 없게 만듭니다 (OP가 원하는 것과 반대 임).
Matt

1
@Matt의 답변 포인트 (a)에 대한 후속 조치 : 사용 presentationControllerDidDismiss이 작동해야합니다
gondo

presentationControllerDidAttemptToDismiss사용자가 해제하려고했지만 프로그래밍 방식으로 금지 된 경우를위한 것이기 때문에 정확하지 않습니다 (해당 방법에 대한 문서를주의 깊게 읽으십시오). 이 presentationControllerWillDismiss방법은 사용자의 해고 의도를 감지하거나 해고 presentationControllerShouldDismiss를 제어 하거나 해고 presentationControllerDidDismiss사실을 감지하는 방법입니다
Vitalii

5

빠른

호출에 일반 솔루션 viewWillAppeariOS13

class ViewController: UIViewController {

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("viewWillAppear")
        }

        //Show new viewController
        @IBAction func show(_ sender: Any) {
            let newViewController = NewViewController()
            //set delegate of UIAdaptivePresentationControllerDelegate to self
            newViewController.presentationController?.delegate = self
            present(newViewController, animated: true, completion: nil)
        }
    }

    extension UIViewController: UIAdaptivePresentationControllerDelegate {
        public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
            if #available(iOS 13, *) {
                //Call viewWillAppear only in iOS 13
                viewWillAppear(true)
            }
        }
    }

1
이것은 함수를 호출하지 않고 맨 위에서 슬라이드를 사용하여 닫기 만 처리합니다 dismiss(_).
Pedro Paulo Amorim

3

DRAG OR CALL DISMISS FUNC는 아래 코드에서 작동합니다.

1) 루트 뷰 컨트롤러에서 아래 코드와 같이 프레젠테이션 뷰 컨트롤러가 무엇인지 알려줍니다.

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "presenterID" {
        let navigationController = segue.destination as! UINavigationController
        if #available(iOS 13.0, *) {
            let controller = navigationController.topViewController as! presentationviewcontroller
            // Modal Dismiss iOS 13
            controller.presentationController?.delegate = self
        } else {
            // Fallback on earlier versions
        }
        navigationController.presentationController?.delegate = self

    }
}

2) 다시 루트 뷰 컨트롤러에서 프레젠테이션 뷰 컨트롤러가 해산 될 때 수행 할 작업을 알려줍니다.

public func presentationControllerDidDismiss(
  _ presentationController: UIPresentationController)
{
    print("presentationControllerDidDismiss")
}

1) 프레젠테이션 뷰 컨트롤러에서이 그림에서 취소 또는 저장 버튼을 누르면. 아래 코드가 호출됩니다.

self.dismiss(animated: true) {
        self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!)
    }

여기에 이미지 설명 입력


1
navigationController.topViewController를 presentationViewController로 캐스트해야합니까? 나는 그렇지 찾을
Fitsyu

취소 버튼 자식 VC에서 해제 한 후 부모 VC에서 데이터를 다시로드하려면 어떻게해야합니까?
Krishna Meena

3

해제되는 UIViewController에서 viewWillDisappear를 재정의합니다. isBeingDismissed부울 플래그 를 통해 해고를 알립니다 .

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isBeingDismissed {
        print("user is dismissing the vc")
    }
}

** 사용자가 아래로 스 와이프하고 카드를 다시 위로 스 와이프하면 카드가 해제되지 않은 경우에도 해제 된 것으로 등록됩니다. 그러나 그것은 당신이 신경 쓰지 않을 수있는 가장 중요한 경우입니다.


어떨까요self.dismiss(animated: Bool, completion: (() -> Void)?)
iGhost

0

사용자가 해당 시트 내에서 모달 시트를 닫을 때 작업을 수행하려는 경우. @IBAction닫거나 다른 작업을 수행하기 전에 경고를 표시하는 및 논리 가있는 닫기 버튼이 이미 있다고 가정 해 보겠습니다 . 사용자가 그러한 컨트롤러를 누르는 순간을 감지하고 싶을뿐입니다.

방법은 다음과 같습니다.

class MyModalSheetViewController: UIViewController {

     override func viewDidLoad() {
        super.viewDidLoad()

        self.presentationController?.delegate = self
     }

     @IBAction func closeAction(_ sender: Any) {
         // your logic to decide to close or not, when to close, etc.
     }

}

extension MyModalSheetViewController: UIAdaptivePresentationControllerDelegate {

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        return false // <-prevents the modal sheet from being closed
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        closeAction(self) // <- called after the modal sheet was prevented from being closed and leads to your own logic
    }
}

-1

누군가가되게 뷰 컨트롤러에 액세스 할 수없는 경우, 그들은 단지 뷰 컨트롤러를 제시에 다음과 같은 방법을 무시하고 변경할 수 있습니다 modalPresentationStylefullScreen또는 전략 중 하나는이 방법으로 위에서 언급 한 추가 할 수 있습니다

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if let _ = viewControllerToPresent as? TargetVC {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    super.present(viewControllerToPresent, animated: flag, completion: completion)
}

제시된 뷰 컨트롤러가 네비게이션 컨트롤러이고 루트 컨트롤러를 확인하려면 위의 조건을 다음과 같이 변경할 수 있습니다.

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}

-2

ModalPresentationStyle을 FullScreen에서 사용한 경우 컨트롤러의 동작이 평소대로 돌아옵니다.

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController ( "ConsultarController") as ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController (controllerConsultar, true);


기존 답변을 반복합니다.
매트

-3

내 관점에서, 설정하지 말아야 애플은 pageSheet기본입니다modalPresentationStyle

fullScreen사용하여 스타일을 기본값으로 되돌리고 싶습니다.swizzling

이렇게 :

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static func preventPageSheetPresentationStyle () {
        UIViewController.preventPageSheetPresentation
    }

    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

그리고이 줄을 당신의 AppDelegate

UIViewController.preventPageSheetPresentationStyle()

1
이것은 독창적이지만 동의 할 수 없습니다. 그것은 당신이하는 아이폰 OS (13)의 성미에가는, 더 많은 지점으로, 해키 및 가정 아이폰 OS 우리의 응답 애플 예상하는 13에 "카드"프리젠 테이션을 사용하는 "주변 작업"되지 않는다; 그것은 "그것을 극복"입니다.
matt

귀하의 요점에 동의하십시오.이 솔루션은 Apple이 권장하는 카드 프레젠테이션 스타일을 사용하는 데 도움이되지 않습니다. 그러나 기본 스타일로 설정하면 presentingViewController트리거되지 않기 때문에 기존 코드 줄이 어딘가에서 실수 하게됩니다viewWillAppear
jacob

1
예,하지만 이미 내 대답에서 말했듯이 전체 화면이 아닌 프레젠테이션 (예 : iPad의 팝 오버 및 페이지 / 양식 시트)의 경우 항상 문제 였으므로 이것은 새로운 것이 아닙니다. 이제 더 많은 것이 있습니다. 의지하는 viewWillAppear것은 어떤 의미에서 항상 잘못되었습니다. 물론 나는 애플이 와서 내 아래에서 바닥을 자르는 것을 좋아하지 않는다. 하지만 제가 말했듯이, 우리는 그와 함께 살아가고 새로운 방식으로 일을해야합니다.
matt

내 프로젝트에는 뷰 컨트롤러 (라고 함 presentedController)가 어디에 있는지 모르고 정확히 presentingViewController. 예를 들어, 어떤 경우 UIViewController.topMostViewController()에는 현재 창에서 가장 상위 뷰 컨트롤러를 반환하는 것을 사용해야 합니다. 그래서 viewWillAppear내 뷰 컨트롤러 에서 올바른 작업 (데이터 새로 고침, UI)을 수행하기 위해 현재 동작을 유지하기 위해 스위 즐링을하고 싶습니다. 이 문제를 해결하기위한 아이디어가 있으면 도와주세요.
야곱

글쎄, 내 대답의 끝에 연결하는 해결책은 그것을 해결하는 데 효과적이라고 믿습니다. 프레젠테이션 시간에 구성하는 데 약간의 작업이 필요하지만 기본적으로 모든 발표자 (경고 발표자 포함)가 프레젠테이션 된보기 컨트롤러가 해제 될 때 듣도록 보장합니다.
matt

-6

presentingViewController.viewWillAppear를 호출하는 것이 간단하지 않습니까? 해고하기 전에?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)

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