使用来关闭模态视图控制器时dismissViewController
,可以选择提供完成块。是否有类似的等效项popViewController
?
完成参数很方便。例如,我可以使用它来阻止从表视图中删除行,直到模式退出屏幕,让用户看到行动画。从推送视图控制器返回时,我希望有同样的机会。
我尝试放置popViewController
在UIView
动画块中,但我确实可以访问完成块。但是,这会对弹出的视图产生一些不必要的副作用。
如果没有这样的方法,有什么解决方法?
使用来关闭模态视图控制器时dismissViewController
,可以选择提供完成块。是否有类似的等效项popViewController
?
完成参数很方便。例如,我可以使用它来阻止从表视图中删除行,直到模式退出屏幕,让用户看到行动画。从推送视图控制器返回时,我希望有同样的机会。
我尝试放置popViewController
在UIView
动画块中,但我确实可以访问完成块。但是,这会对弹出的视图产生一些不必要的副作用。
如果没有这样的方法,有什么解决方法?
Answers:
我知道两年前就已经接受了答案,但是这个答案是不完整的。
开箱即用地做您想做的事
从技术上讲这是正确的,因为UINavigationController
API对此不提供任何选项。但是,通过使用CoreAnimation框架,可以向基础动画添加完成块:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// handle completion here
}];
[self.navigationController popViewControllerAnimated:YES];
[CATransaction commit];
结束使用的动画会立即调用完成块popViewControllerAnimated:
。此功能自iOS 4起可用。
extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
对于iOS9 SWIFT版本-就像一个超级按钮(尚未针对较早版本进行测试)。根据这个答案
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()
}
}
}
我Swift
用@JorisKluivers答案制作了带有扩展名的版本。
为push
和制作完动画后,这将称为完成关闭pop
。
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()
}
}
popViewController
或之后调用pushViewController
,但是如果之后检查topViewController是什么,您会发现它仍然是旧的,就像pop
或push
从未发生过……
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()
}
}
我遇到过同样的问题。而且由于必须多次在完成块链中使用它,因此我在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];
}
假设
@interface NavigationController : UINavigationController <UINavigationControllerDelegate>
和
@implementation NavigationController {
void (^_completion)();
}
和
- (id) initWithRootViewController:(UIViewController *) rootViewController {
self = [super initWithRootViewController:rootViewController];
if (self) {
self.delegate = self;
}
return self;
}
开箱即用,无法做您想做的事情。即没有带有完成块的方法可以从导航堆栈中弹出视图控制器。
我要做的是把逻辑放进去viewDidAppear
。当视图完成显示在屏幕上时,将调用该方法。会在视图控制器出现的所有不同情况下调用它,但这应该没问题。
或者,您可以使用该UINavigationControllerDelegate
方法navigationController:didShowViewController:animated:
执行类似的操作。当导航控制器完成推入或弹出视图控制器时,将调用此方法。
dismissViewController
。也许到了popViewController
。归档雷达:-)。
-dismissViewController:animated:completionBlock:
,但是可以通过导航控制器的委托来获取动画。动画完成后,-navigationController:didShowViewController:animated:
将在委托上调用该动画,您可以在那里立即执行所需的任何操作。
正确处理或不处理动画,还包括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)
}
}
completion()
异步的任何特殊原因?
completion
器进行动画时,永远不要在同一运行循环上执行。这样可以保证completion
在没有动画时永远不会在同一个runloop上运行。最好不要出现这种不一致的情况。
如果你有这个...
navigationController?.popViewController(animated: false)
// I want this to happen next, help! ->
nextStep()
而你想添加一个完成...
CATransaction.begin()
navigationController?.popViewController(animated: true)
CATransaction.setCompletionBlock({ [weak self] in
self?.nextStep() })
CATransaction.commit()
就这么简单。
方便的popToViewController
通话也一样。
一个典型的情况是,您有一个成千上万个屏幕的入门堆栈。最终完成后,您将一直回到“基本”屏幕,然后最终启动该应用程序。
因此,在“基本”屏幕中,“一直返回”, 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()
}
在提供的视图控制器上调用viewDidDisappear方法之后,将调用完成块,因此将代码放入弹出的视图控制器的viewDidDisappear方法中应与完成块相同。
Swift 3的答案,感谢这个答案: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()
}
}
Swift 4版本,带有可选的viewController参数,可以弹出特定的参数。
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()
}
}
}
根据此答案清理了Swift 4版本。
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()
}
}
}
有一个名为UINavigationControllerWithCompletionBlock的pod ,当在UINavigationController上推入和弹出时,它添加了对完成块的支持。
2020 Swift 5.1方式
此解决方案保证在popViewController完全完成之后执行完成。您可以通过完成对NavigationController的其他操作来对其进行测试:在上述所有其他解决方案中,UINavigationController仍在忙于popViewController操作,并且没有响应。
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)
}
}
为了完整起见,我已经准备好要使用的Objective-C类别:
// 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
我使用一个块精确地实现了这一目标。我希望获取的结果控制器仅在完全离开屏幕后才显示模式视图添加的行,以便用户可以看到发生的更改。在准备负责显示模态视图控制器的segue时,我设置了模态消失时要执行的块。在模态视图控制器中,我重写viewDidDissapear,然后调用该块。我只是在模态即将出现时开始更新,而在模态消失时结束更新,但这是因为我使用的是NSFetchedResultsController,但是您可以在块内执行任何操作。
-(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
在代码上使用下一个扩展:(快速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()
}
}