Phát hiện khi bộ điều khiển chế độ xem được trình bày bị loại bỏ


81

Giả sử, tôi có một phiên bản của lớp điều khiển chế độ xem được gọi là VC2. Trong VC2, có một nút "hủy bỏ" sẽ tự loại bỏ. Nhưng tôi không thể phát hiện hoặc nhận được bất kỳ cuộc gọi lại nào khi nút "hủy" được kích hoạt. VC2 là một hộp đen.

Một bộ điều khiển khung nhìn (được gọi là VC1) sẽ trình bày VC2 bằng presentViewController:animated:completion:phương thức.

VC1 có những tùy chọn nào để phát hiện khi VC2 bị loại bỏ?

Chỉnh sửa: Từ nhận xét của @rory mckinnel và câu trả lời của @NicolasMiari, tôi đã thử như sau:

Trong VC2:

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

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

Trong 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:^{
//        
//    }];
}

Nhưng dismissViewControllerAnimatedtrong VC1 đã không được gọi.


1
trong VC1, phương thức viewWillAppear sẽ được gọi
Istvan

1
Theo tài liệu, người kiểm soát trình bày chịu trách nhiệm về việc sa thải thực tế. Khi người điều khiển được trình bày tự loại bỏ, nó sẽ yêu cầu người trình bày làm điều đó cho mình. Vì vậy, nếu bạn ghi đè dismissViewControllerAnimatedbộ điều khiển VC1 của mình, tôi tin rằng nó sẽ được gọi khi bạn nhấn hủy trên VC2. Phát hiện loại bỏ và sau đó gọi phiên bản siêu lớp sẽ thực hiện loại bỏ thực tế.
Rory McKinnel

1
Bạn có thể kiểm tra ghi đè của mình bằng cách gọi điện [self.presentingViewController dismissViewControllerAnimated]. Có thể mã bên trong có một cơ chế khác để yêu cầu người trình bày thực hiện việc loại bỏ.
Rory McKinnel

@RoryMcKinnel: Việc sử dụng self.presentingViewController đã hoạt động trong phòng thí nghiệm VC2 của tôi cũng như từ hộp đen thực. Nếu bạn đưa ý kiến ​​của bạn vào câu trả lời thì tôi sẽ chọn nó làm câu trả lời. Cảm ơn.
user523234 30/09/15

Giải pháp cho vấn đề này có thể được tìm thấy trong bài đăng liên quan này: stackoverflow.com/a/34571641/3643020
Campbell_Souped

Câu trả lời:


64

Theo tài liệu, người kiểm soát trình bày chịu trách nhiệm về việc sa thải thực tế. Khi người điều khiển được trình bày tự giải quyết, nó sẽ yêu cầu người trình bày làm điều đó cho mình. Vì vậy, nếu bạn ghi đè lên DisViewControllerAnimated trong bộ điều khiển VC1 của mình, tôi tin rằng nó sẽ được gọi khi bạn nhấn hủy trên VC2. Phát hiện loại bỏ và sau đó gọi phiên bản siêu lớp sẽ thực hiện loại bỏ thực tế.

Như tìm thấy từ cuộc thảo luận, điều này dường như không hoạt động. Thay vì dựa vào cơ chế cơ bản, thay vì gọi dismissViewControllerAnimated:completiontrên VC2 bản thân, cuộc gọi dismissViewControllerAnimated:completiontrên self.presentingViewControllertrong VC2. Điều này sau đó sẽ gọi trực tiếp ghi đè của bạn.

Một cách tiếp cận tốt hơn hoàn toàn sẽ là để VC2 cung cấp một khối được gọi khi bộ điều khiển phương thức đã hoàn thành.

Vì vậy, trong VC2, hãy cung cấp một thuộc tính khối nói với tên onDoneBlock.

Trong VC1 bạn trình bày như sau:

  • Trong VC1, tạo VC2

  • Đặt trình xử lý đã hoàn thành cho VC2 là: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • Trình bày bộ điều khiển VC2 như bình thường bằng cách sử dụng [self presentViewController: VC2 animation: YES finish: nil];

  • Trong VC2, trong lệnh gọi hành động mục tiêu hủy self.onDoneBlock();

Kết quả là VC2 cho bất cứ ai nâng nó lên rằng nó đã được thực hiện. Bạn có thể mở rộng onDoneBlockđể có các đối số cho biết nếu phương thức đã bắt đầu, hủy bỏ, thành công, v.v.


2
Chỉ muốn cảm ơn và đánh giá cao cách hoạt động tuyệt vời của nó..sau 4 năm! Cảm ơn bạn!
Anna

45

Có một thuộc tính Boolean đặc biệt bên trong UIViewControllerđược gọi là isBeingDismissedbạn có thể sử dụng cho mục đích này:

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

3
Câu trả lời dễ nhất hay nhất, giải quyết chính xác hầu hết các vấn đề và không cần triển khai thêm.
tái phạm

Nó không hoạt động chính xác nếu không ghép nối với viewDidAppear.
Dmitry

9
Trong bản trình bày phương thức iOS13, điều này sẽ đúng khi người dùng bắt đầu kéo bộ điều khiển để loại bỏ, nhưng họ có thể chọn không hoàn thành việc loại bỏ.
Estel

44

Sử dụng thuộc tính khối

Khai báo trong VC2

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

Thiết lập trong VC1

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

Gọi trong VC2 khi bạn sắp loại bỏ

onDoneBlock!(true)

@ Bryce64 Nó không hoạt động với tôi, tôi nhận được "Chủ đề 1: Lỗi nghiêm trọng: Không ngờ được tìm thấy nil trong khi mở gói một giá trị Tùy chọn", tại thời điểm mã chuyển đến onDoneBlock! (True)
Lucas

@Lucas Có vẻ như bạn đã khai báo không đúng trong VC1. Các "!" buộc việc mở ra buộc lỗi nếu bạn không thiết lập nó một cách chính xác.
brycejl 21/02/18

1
Giả sử chỉ có một View Controller được trình bày. Bạn có thể ở trên một ngăn xếp điều hướng mà thần biết ở đâu.
Lee Probert,

@LeeProbert Chính xác. Chúng tôi có một bộ điều khiển Điều hướng được trình bày với khoảng 10 bộ điều khiển con có thể có bên cạnh ngăn xếp của nó và hầu như mọi bộ điều khiển trong số chúng đều có thể kích hoạt việc loại bỏ ... trong tình huống này, bất kỳ khối hoàn thành nào sẽ phải được chuyển cho tất cả 10 bộ điều khiển như vậy
Igor Vasilev

13

Cả bộ điều khiển chế độ xem được trình bày được trình bày đều có thể gọi dismissViewController:animated:để loại bỏ bộ điều khiển chế độ xem được trình bày.

Tùy chọn trước đây (được cho là) ​​là lựa chọn "đúng", phù hợp với thiết kế: Bộ điều khiển chế độ xem "mẹ" giống nhau chịu trách nhiệm trình bày và loại bỏ bộ điều khiển chế độ xem phương thức ("con").

Tuy nhiên, cách sau tiện lợi hơn: thông thường, nút "loại bỏ" được gắn vào chế độ xem của bộ điều khiển chế độ xem được trình bày và nó cho biết bộ điều khiển chế độ xem được đặt làm mục tiêu hành động của nó.

Nếu bạn đang áp dụng cách tiếp cận cũ, bạn đã biết dòng mã trong bộ điều khiển chế độ xem trình bày của mình nơi xảy ra loại bỏ: hoặc chạy mã của bạn ngay sau dismissViewControllerAnimated:completion: hoặc trong khối hoàn thành.

Nếu bạn đang áp dụng cách tiếp cận sau (bộ điều khiển chế độ xem được trình bày tự loại bỏ), hãy nhớ rằng việc gọi dismissViewControllerAnimated:completion:từ bộ điều khiển chế độ xem được trình bày khiến UIKit lần lượt gọi phương thức đó trên bộ điều khiển chế độ xem trình bày:

Thảo luận

Bộ điều khiển chế độ xem trình bày chịu trách nhiệm loại bỏ bộ điều khiển chế độ xem mà nó đã trình bày. Nếu bạn gọi phương thức này trên chính bộ điều khiển chế độ xem được trình bày, UIKit sẽ yêu cầu bộ điều khiển chế độ xem trình bày xử lý việc loại bỏ.

( nguồn: Tham khảo lớp UIViewController )

Vì vậy, để chặn sự kiện như vậy, bạn có thể ghi đè phương thức đó trong bộ điều khiển chế độ xem trình bày :

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

    // Your custom code here...
}

1
Không vấn đề gì. Nhưng hóa ra nó không hoạt động như mong đợi. Rất may, câu trả lời của @ RoryMcKinnel dường như cung cấp nhiều lựa chọn hơn.
Nicolas Miari

Mặc dù cách tiếp cận này đủ chung chung để phân loại bộ điều khiển chế độ xem từ bộ điều khiển chế độ xem cơ sở, nhưng quảng cáo ghi đè lên bộ điều khiển chế độ xemViewControllerAnimated trong đó. Nhưng nó không thành công Nếu bạn cố gắng để quấn lên trong một ứng dụng điều khiển xem trong diện điều hướng điều khiển
hariszaman

4
Nó không được gọi khi người dùng loại bỏ bộ điều khiển chế độ xem bằng cách vuốt từ trên xuống!
Dmitry

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

vc.presentationController?.delegate = foo

1
iOS 13.0+chỉ
gondo

2

Bạn có thể sử dụng thư giãn segue để thực hiện tác vụ này, không cần phải sử dụng giải pháp gạt bỏ. Xác định phương pháp thư giãn trong VC1 của bạn.

Xem liên kết này về cách tạo segue thư giãn, https://stackoverflow.com/a/15839298/5647055 .

Giả sử xác định thư giãn của bạn được thiết lập, trong phương pháp hành động được xác định cho nút "Hủy", bạn có thể thực hiện xác định dưới dạng:

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

Bây giờ, bất cứ khi nào bạn nhấn nút "Cancel" trong VC2, nó sẽ bị loại bỏ và VC1 sẽ xuất hiện. Nó cũng sẽ gọi phương thức thư giãn, bạn đã xác định trong VC1. Bây giờ, bạn biết khi nào bộ điều khiển dạng xem được trình bày bị loại bỏ.


2

Tôi sử dụng phần sau để báo hiệu cho bộ điều phối rằng bộ điều khiển chế độ xem đã "xong". Điều này được sử dụng trong một AVPlayerViewControllerlớp con trong ứng dụng tvOS và sẽ được gọi sau khi quá trình chuyển đổi loại bỏ playerVC hoàn tất:

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?()
        }
    })
  }
}

Bạn không nên kế thừa từ AVPLayerViewController. Tài liệu của Apple cho biết: "Phân lớp AVPlayerViewController và ghi đè các phương thức của nó không được hỗ trợ và dẫn đến hành vi không xác định."
Neru

2

Sử dụng willMove(toParent: UIViewController?)theo cách sau đây dường như hiệu quả đối với tôi. (Đã thử nghiệm trên iOS12).

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

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

1

@ user523234 - "Nhưng bác bỏViewControllerAnimated trong VC1 không được gọi."

Bạn không thể cho rằng VC1 thực sự thực hiện trình bày - nó có thể là bộ điều khiển chế độ xem gốc, VC0, chẳng hạn. Có 3 bộ điều khiển chế độ xem liên quan:

  • sourceViewController
  • PresentationViewController
  • PresentViewController

Trong ví dụ của bạn, VC1 = sourceViewController, VC2 = presentedViewController, ?? = presentingViewController- có thể VC1, có thể không.

Tuy nhiên, bạn luôn có thể dựa vào VC1.animationControllerForDismissedController được gọi (nếu bạn đã triển khai các phương thức ủy quyền) khi loại bỏ VC2 và trong phương thức đó, bạn có thể làm những gì bạn muốn với VC1


1

Tôi đã xem bài đăng này rất nhiều lần khi giải quyết vấn đề này, tôi nghĩ cuối cùng tôi có thể làm sáng tỏ một câu trả lời khả thi.

Nếu những gì bạn cần là biết liệu các hành động do người dùng khởi tạo (như cử chỉ trên màn hình) có thực hiện loại bỏ UIActionController hay không hay không và không muốn đầu tư thời gian vào việc tạo các lớp con hoặc phần mở rộng hoặc bất cứ thứ gì trong mã của bạn, thì có một giải pháp thay thế.

Hóa ra, thuộc tính popoverPresentationController của một UIActionController (hay đúng hơn là bất kỳ UIViewController nào có hiệu lực đó), có một đại biểu mà bạn có thể đặt bất kỳ lúc nào trong mã của mình, thuộc loại UIPopoverPresentationControllerDelegate và có các phương thức sau:

Chỉ định người được ủy quyền từ bộ điều khiển hành động của bạn, triển khai (các) phương thức bạn chọn trong lớp đại biểu (chế độ xem, bộ điều khiển chế độ xem hoặc bất cứ thứ gì), và thì đấy!

Hi vọng điêu nay co ich.


Và những thứ đó không còn được dùng nữa kể từ iOS 13. Doh
Pretbird

0
  1. Tạo một tệp lớp (.h / .m) và đặt tên là: DismissSegue
  2. Chọn Lớp con của: UIStoryboardSegue

  3. Đi tới tệp DismissSegue.m và viết ra mã sau:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. Mở bảng phân cảnh & sau đó Ctrl + kéo từ nút hủy đến VC1 và chọn Hành động phân biệt là Loại bỏ và bạn đã hoàn tất.


0

Nếu bạn ghi đè trên bộ điều khiển chế độ xem đang bị bỏ qua:

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

Ít nhất điều này đã làm việc cho tôi.


@JohnScalo không đúng, khá nhiều cấu trúc phân cấp bộ điều khiển chế độ xem “gốc” tự triển khai với các nguyên thủy con / cha.
mxcl


0

overrideing viewDidAppearđã làm thủ thuật cho tôi. Tôi đã sử dụng Singleton trong phương thức của mình và bây giờ có thể thiết lập và lấy từ đó trong VC đang gọi, phương thức và mọi nơi khác.


viewDidAppear?
Dmitry

1
Ý của bạn là viewDidDisappear?
Dale

0

viewWillDisappearChức năng ghi đè trong bộ điều khiển chế độ xem được trình bày.

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

Nó không hoạt động chính xác nếu không ghép nối với viewDidAppear.
Dmitry

1
Đảm bảo gọi super.viewWillDisappear
Dale

0

Như đã được đề cập, giải pháp là sử dụng override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil).

Đối với những người tự hỏi tại sao override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)dường như không luôn hoạt động, bạn có thể thấy rằng cuộc gọi đang bị chặn bởi một UINavigationControllernếu nó đang được quản lý. Tôi đã viết một lớp con sẽ giúp:

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

Nếu bạn muốn xử lý việc loại bỏ bộ điều khiển chế độ xem, bạn nên sử dụng mã bên dưới.

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

Rất tiếc, chúng tôi không thể gọi hoàn thành trong phương thức ghi đè - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;vì phương thức này chỉ được gọi nếu bạn gọi phương thức loại bỏ của bộ điều khiển chế độ xem này.


Nhưng viewWillDisappearcũng không hoạt động chính xác nếu không ghép nối với viewDidAppear.
Dmitry

viewWillDisappear được gọi khi VC bị che hoàn toàn (Ví dụ: với một phương thức). Bạn có thể đã không bị sa thải
Lou Franco

0

Một tùy chọn khác là lắng nghe sa thảiTransitionDidEnd () của UIPresentationController tùy chỉnh của bạn


0

Tôi đã sử dụng deinit cho ViewController

deinit {
    dataSource.stopUpdates()
}

Một deinitializer được gọi ngay lập tức trước khi một cá thể lớp được phân bổ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.