Khối hoàn thành cho popViewController


113

Khi loại bỏ bộ điều khiển chế độ xem phương thức bằng cách sử dụng dismissViewController, có tùy chọn cung cấp khối hoàn thành. Có một tương đương tương tự cho popViewController?

Đối số hoàn thành khá tiện dụng. Ví dụ: tôi có thể sử dụng nó để tạm dừng xóa một hàng khỏi chế độ xem bảng cho đến khi phương thức tắt màn hình, cho phép người dùng xem hoạt ảnh của hàng. Khi quay lại từ bộ điều khiển chế độ xem được đẩy, tôi cũng muốn có cơ hội tương tự.

Tôi đã thử đặt popViewControllervào một UIViewkhối hoạt ảnh, nơi tôi có quyền truy cập vào một khối hoàn thành. Tuy nhiên, điều này tạo ra một số tác dụng phụ không mong muốn trên chế độ xem được bật lên.

Nếu không có phương pháp nào như vậy thì có một số cách giải quyết?


stackoverflow.com/a/33767837/2774520 tôi nghĩ rằng cách này là một trong những nguồn gốc nhất
Oleksii Nezhyborets


3
Đối với năm 2018, điều này rất đơn giản và tiêu chuẩn: stackoverflow.com/a/43017103/294884
Fattie

Câu trả lời:


199

Tôi biết một câu trả lời đã được chấp nhận hơn hai năm trước, tuy nhiên câu trả lời này không đầy đủ.

Không có cách nào để làm những gì bạn muốn

Điều này đúng về mặt kỹ thuật vì UINavigationControllerAPI không cung cấp bất kỳ tùy chọn nào cho việc này. Tuy nhiên, bằng cách sử dụng khung CoreAnimation, bạn có thể thêm một khối hoàn thành vào hoạt ảnh bên dưới:

[CATransaction begin];
[CATransaction setCompletionBlock:^{
    // handle completion here
}];

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Khối hoàn thành sẽ được gọi ngay khi hoạt ảnh được sử dụng popViewControllerAnimated:kết thúc. Chức năng này đã có từ iOS 4.


5
Tôi đặt điều này trong một phần mở rộng của UINavigationController bằng Swift:extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
Arbitur

1
Dường như không hiệu quả đối với tôi, khi tôi thực hiện CompleteHandler trên allowViewController, chế độ xem đang trình bày nó là một phần của hệ thống phân cấp chế độ xem. Khi tôi làm tương tự với CATransaction, tôi nhận được cảnh báo rằng chế độ xem không phải là một phần của hệ thống phân cấp chế độ xem.
moger777

1
OK, có vẻ như tác phẩm của bạn nếu bạn đảo ngược khối bắt đầu và hoàn thành. Xin lỗi về sự bỏ phiếu xuống nhưng stack overflow sẽ không cho phép tôi thay đổi :(
moger777

7
Vâng, điều này có vẻ như sẽ rất tuyệt vời, nhưng nó dường như không hoạt động (ít nhất là trên iOS 8). Khối hoàn thành được gọi ngay lập tức. Có thể là do sự kết hợp giữa hoạt ảnh cốt lõi với hoạt ảnh phong cách UIView.
kẹt vào

5
ĐIỀU NÀY KHÔNG HOẠT ĐỘNG
durazno

51

Đối với phiên bản SWIFT iOS9 - hoạt động như một sự quyến rũ (chưa được thử nghiệm cho các phiên bản trước đó). Dựa trên câu trả lời này

extension UINavigationController {    
    func pushViewController(viewController: UIViewController, animated: Bool, completion: () -> ()) {
        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }

    func popViewController(animated: Bool, completion: () -> ()) {
        popViewControllerAnimated(animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

Sẽ không hoạt động nếu không hoạt ảnh, nên hoàn thành trong lần chạy tiếp theo để thực hiện đúng cách.
rshev

@rshev tại sao vào runloop tiếp theo?
Ben Sinclair

@Andy từ những gì tôi nhớ đã thử nghiệm điều này, một cái gì đó vẫn chưa được truyền bá vào thời điểm đó. Hãy thử trải nghiệm nó, thích nghe nó hoạt động như thế nào đối với bạn.
rshev

@rshev Tôi nghĩ trước đây tôi cũng bị như vậy, tôi phải kiểm tra lại. Các bài kiểm tra hiện tại chạy tốt.
Ben Sinclair

1
@LanceSamaria Tôi khuyên bạn nên sử dụng viewDidDisappear. Kiểm tra xem thanh điều hướng có khả dụng không, nếu không - thanh điều hướng không được hiển thị trong thanh điều hướng, vì vậy nó đã được bật lên. if (self.navigationController == nil) {kích hoạt hành động của bạn}
HotJard

32

Tôi đã tạo một Swiftphiên bản có tiện ích mở rộng với câu trả lời @JorisKluivers .

Điều này sẽ gọi một đóng hoàn thành sau khi hoạt ảnh được thực hiện cho cả pushpop.

extension UINavigationController {
    func popViewControllerWithHandler(completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewControllerAnimated(true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

Đối với tôi, trong iOS 8.4, được viết bằng ObjC, khối này sẽ kích hoạt một nửa hoạt ảnh. Điều này có thực sự bùng nổ vào đúng thời điểm nếu được viết bằng Swift (8.4) không?
Julian F. Weinert

Khối hoàn @Arbitur thực sự được gọi sau khi gọi popViewControllerhay pushViewController, nhưng nếu bạn kiểm tra những gì topViewController là ngay sau đó, bạn sẽ thấy nó vẫn là cái cũ, giống như pophoặc pushkhông bao giờ xảy ra ...
Bogdan Razvan

@BogdanRazvan ngay sau đó là gì? Đóng hoàn thành của bạn có được gọi sau khi hoạt ảnh hoàn tất không?
Arbitur

17

SWIFT 4.1

extension UINavigationController {
func pushToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.pushViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popViewController(animated: animated)
    CATransaction.commit()
}

func popToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popToRootViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToRootViewController(animated: animated)
    CATransaction.commit()
}
}

17

Tôi gặp vấn đề tương tự. Và bởi vì tôi phải sử dụng nó nhiều lần và trong chuỗi các khối hoàn thành, tôi đã tạo giải pháp chung này trong một lớp con UINavigationController:

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        dispatch_async(dispatch_get_main_queue(),
        ^{
            _completion();
            _completion = nil;
         });
    }
}

- (UIViewController *) popViewControllerAnimated:(BOOL) animated completion:(void (^)()) completion {
    _completion = completion;
    return [super popViewControllerAnimated:animated];
}

Giả định

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

@implementation NavigationController {
    void (^_completion)();
}

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}

1
Tôi thực sự thích giải pháp này, tôi sẽ thử nó với một danh mục và một đối tượng liên quan.
spstanley,

@spstanley bạn cần xuất bản nhóm này :)
k06a


15

Không có cách nào để làm những gì bạn mong muốn. tức là không có phương thức nào có khối hoàn thành để đưa bộ điều khiển chế độ xem từ ngăn xếp điều hướng.

Những gì tôi sẽ làm là đưa logic vào viewDidAppear. Điều đó sẽ được gọi khi chế độ xem hoàn tất xuất hiện trên màn hình. Nó sẽ được gọi cho tất cả các tình huống khác nhau của bộ điều khiển chế độ xem xuất hiện, nhưng điều đó sẽ ổn.

Hoặc bạn có thể sử dụng UINavigationControllerDelegatephương pháp này navigationController:didShowViewController:animated:để làm điều tương tự. Điều này được gọi khi bộ điều khiển điều hướng đã hoàn tất việc đẩy hoặc bật một bộ điều khiển chế độ xem.


Tôi đã cố gắng điều này. Tôi đang lưu trữ một mảng 'chỉ mục hàng đã xóa' và bất cứ khi nào chế độ xem xuất hiện, hãy kiểm tra xem có cần xóa bất kỳ thứ gì không. Nó nhanh chóng trở nên khó sử dụng nhưng tôi có thể cho nó một lần nữa. Tôi tự hỏi tại sao Apple cung cấp nó cho một quá trình chuyển đổi mà không phải là chuyển đổi khác?
Ben Packard

1
Nó chỉ rất mới trên dismissViewController. Có lẽ nó sẽ đến popViewController. Gửi một radar :-).
mattjgalloway

Nghiêm túc mà nói, hãy khai báo radar. Nó có nhiều khả năng đạt được nếu mọi người yêu cầu.
mattjgalloway

1
Đó là nơi thích hợp để yêu cầu nó. Có một tùy chọn cho phân loại là 'Tính năng'.
mattjgalloway

3
Câu trả lời này không hoàn toàn chính xác. Mặc dù bạn không thể đặt khối kiểu mới như trên-dismissViewController:animated:completionBlock: , nhưng bạn có thể lấy hoạt ảnh thông qua đại biểu của bộ điều khiển điều hướng. Sau khi hoạt ảnh hoàn tất, -navigationController:didShowViewController:animated:sẽ được gọi trên đại diện và bạn có thể làm bất cứ điều gì bạn cần ngay tại đó.
Jason Coco

13

Làm việc có hoặc không có hoạt ảnh đúng cách và cũng bao gồm popToRootViewController:

 // updated for Swift 3.0
extension UINavigationController {

  private func doAfterAnimatingTransition(animated: Bool, completion: @escaping (() -> Void)) {
    if let coordinator = transitionCoordinator, animated {
      coordinator.animate(alongsideTransition: nil, completion: { _ in
        completion()
      })
    } else {
      DispatchQueue.main.async {
        completion()
      }
    }
  }

  func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() ->     Void)) {
    pushViewController(viewController, animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popToRootViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popToRootViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }
}

Bất kỳ lý do cụ thể nào khiến bạn gọi là completion()không đồng bộ?
leviathan

1
khi hoạt ảnh với điều phối viên completionkhông bao giờ được thực thi trên cùng một runloop. điều này đảm bảo completionkhông bao giờ chạy trên cùng một runloop khi không tạo hoạt ảnh. tốt hơn là không có loại mâu thuẫn này.
rshev

11

Dựa trên câu trả lời của @ HotJard, khi tất cả những gì bạn muốn chỉ là một vài dòng mã. Nhanh chóng và dễ dàng.

Swift 4 :

_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
    doWhatIWantAfterContollerHasPopped()
}

6

Đối với năm 2018 ...

nếu bạn có cái này ...

    navigationController?.popViewController(animated: false)
    // I want this to happen next, help! ->
    nextStep()

và bạn muốn thêm một hoàn thành ...

    CATransaction.begin()
    navigationController?.popViewController(animated: true)
    CATransaction.setCompletionBlock({ [weak self] in
       self?.nextStep() })
    CATransaction.commit()

nó đơn giản mà.

Mẹo tiện dụng ...

Đó là một thỏa thuận tương tự cho popToViewControllercuộc gọi tiện dụng .

Một điều điển hình là bạn có một chồng hàng triệu màn hình tích hợp. Cuối cùng khi hoàn tất, bạn quay trở lại màn hình "cơ sở" của mình, rồi cuối cùng khởi động ứng dụng.

Vì vậy, trong màn hình "cơ sở", để quay lại "tất cả các con đường trở lại", popToViewController(self

func onboardingStackFinallyComplete() {
    
    CATransaction.begin()
    navigationController?.popToViewController(self, animated: false)
    CATransaction.setCompletionBlock({ [weak self] in
        guard let self = self else { return }
        .. actually launch the main part of the app
    })
    CATransaction.commit()
}

5

Khối hoàn thành được gọi sau khi phương thức viewDidDisappear được gọi trên bộ điều khiển dạng xem được trình bày, Vì vậy, việc đặt mã vào phương thức viewDidDisappear của bộ điều khiển dạng xem bật lên sẽ hoạt động giống như khối hoàn thành.


Chắc chắn - ngoại trừ sau đó, bạn phải xử lý tất cả các trường hợp chế độ xem biến mất vì một số lý do khác.
Ben Packard

1
@BenPackard, vâng và điều này cũng đúng khi đưa nó vào viewDidAppear trong câu trả lời bạn đã chấp nhận.
rdelmar

5

Câu trả lời Swift 3, nhờ câu trả lời này: https://stackoverflow.com/a/28232570/3412567

    //MARK:UINavigationController Extension
extension UINavigationController {
    //Same function as "popViewController", but allow us to know when this function ends
    func popViewControllerWithHandler(completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewController(animated: true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

4

Phiên bản Swift 4 với tham số viewController tùy chọn để bật lên một phiên bản cụ thể.

extension UINavigationController {
    func pushViewController(viewController: UIViewController, animated: 
        Bool, completion: @escaping () -> ()) {

        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
}

func popViewController(viewController: UIViewController? = nil, 
    animated: Bool, completion: @escaping () -> ()) {
        if let viewController = viewController {
            popToViewController(viewController, animated: animated)
        } else {
            popViewController(animated: animated)
        }

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

Câu trả lời được chấp nhận dường như hoạt động trong môi trường nhà phát triển của tôi với tất cả các trình giả lập / thiết bị mà tôi có, nhưng tôi vẫn nhận được báo cáo lỗi từ người dùng sản xuất. Không chắc liệu điều này có giải quyết được vấn đề sản xuất hay không, nhưng hãy để tôi ủng hộ nó để ai đó có thể thử nếu nhận được cùng một vấn đề từ câu trả lời được chấp nhận.
Sean

4

Đã làm rõ phiên bản Swift 4 dựa trên câu trả lời này .

extension UINavigationController {
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
        self.pushViewController(viewController, animated: animated)
        self.callCompletion(animated: animated, completion: completion)
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
        let viewController = self.popViewController(animated: animated)
        self.callCompletion(animated: animated, completion: completion)
        return viewController
    }

    private func callCompletion(animated: Bool, completion: @escaping () -> Void) {
        if animated, let coordinator = self.transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}


2

2020 Swift 5.1 cách

Giải pháp này đảm bảo rằng quá trình hoàn thành được thực thi sau khi popViewController hoàn tất. Bạn có thể kiểm tra nó bằng cách thực hiện một thao tác khác trên NavigationController khi hoàn thành: Trong tất cả các giải pháp khác ở trên UINavigationController vẫn bận hoạt động với popViewController và không phản hồi.

public class NavigationController: UINavigationController, UINavigationControllerDelegate
{
    private var completion: (() -> Void)?

    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
    {
        if self.completion != nil {
            DispatchQueue.main.async(execute: {
                self.completion?()
                self.completion = nil
            })
        }
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController?
    {
        self.completion = completion
        return super.popViewController(animated: animated)
    }
}

1

Chỉ để hoàn thiện, tôi đã tạo một danh mục Objective-C sẵn sàng để sử dụng:

// UINavigationController+CompletionBlock.h

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion;

@end
// UINavigationController+CompletionBlock.m

#import "UINavigationController+CompletionBlock.h"

@implementation UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion {
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        completion();
    }];

    UIViewController *vc = [self popViewControllerAnimated:animated];

    [CATransaction commit];

    return vc;
}

@end

1

Tôi đã đạt được chính xác điều này với độ chính xác bằng cách sử dụng một khối. Tôi muốn bộ điều khiển kết quả đã tìm nạp của mình hiển thị hàng đã được thêm bởi chế độ xem phương thức, chỉ khi nó đã hoàn toàn rời khỏi màn hình để người dùng có thể thấy thay đổi đang diễn ra. Để chuẩn bị cho segue chịu trách nhiệm hiển thị bộ điều khiển chế độ xem phương thức, tôi đặt khối mà tôi muốn thực thi khi phương thức biến mất. Và trong bộ điều khiển chế độ xem phương thức, tôi ghi đè viewDidDissapear và sau đó gọi khối. Tôi chỉ bắt đầu cập nhật khi phương thức sẽ xuất hiện và kết thúc cập nhật khi nó biến mất, nhưng đó là vì tôi đang sử dụng NSFetchedResultsController. Tuy nhiên, bạn có thể làm bất cứ điều gì bạn muốn bên trong khối.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"addPassword"]){

        UINavigationController* nav = (UINavigationController*)segue.destinationViewController;
        AddPasswordViewController* v = (AddPasswordViewController*)nav.topViewController;

...

        // makes row appear after modal is away.
        [self.tableView beginUpdates];
        [v setViewDidDissapear:^(BOOL animated) {
            [self.tableView endUpdates];
        }];
    }
}

@interface AddPasswordViewController : UITableViewController<UITextFieldDelegate>

...

@property (nonatomic, copy, nullable) void (^viewDidDissapear)(BOOL animated);

@end

@implementation AddPasswordViewController{

...

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    if(self.viewDidDissapear){
        self.viewDidDissapear(animated);
    }
}

@end

1

Sử dụng phần mở rộng tiếp theo trên mã của bạn: (Swift 4)

import UIKit

extension UINavigationController {

    func popViewController(animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }

    func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}
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.