在执行 unwind segue 后执行 push segue

38

我正在开发一个相机应用,其中相机视图以模态方式显示。完成裁剪后,我执行一个“返回”操作,并跳转到MainPageViewController。(请参见截图)

storyboard

MainPageViewController中的“返回”函数如下所示:

@IBAction func unwindToMainMenu(segue: UIStoryboardSegue) {
    self.performSegueWithIdentifier("Categories", sender: self)
}

在这里,“categories”是从MainPageViewControllerCategoriesTableViewController的push segue标识符。

程序进入unwindToMainMenu函数,但它没有执行推送segue。有什么想法如何解决这个问题吗?

注意:我发现了同样的问题,但答案建议改变storyboard结构。


这个是否正确地展开到 MainPageViewController - Anil Varghese
我可以正确地解开,但是我想在解开后立即执行推送转场到“MainPageViewController”。 - Berkan Ercan
你可以捕捉到segue的结束。参考这个问题:https://dev59.com/AWMl5IYBdhLWcg3winkW - Jean-Baptiste Yunès
7个回答

59
有点晚了,但我找到了一种不使用状态标志来完成此操作的方法。
注意:这仅适用于iOS 9+,因为在iOS9之前,只有自定义segue支持类名称,并且您无法在storyboards中将退出segue声明为自定义segue。
1. 使用UIStoryboardSegueWithCompletion子类化UIStoryboardSegue
class UIStoryboardSegueWithCompletion: UIStoryboardSegue {
    var completion: (() -> Void)?

    override func perform() {
        super.perform()
        if let completion = completion {
            completion()
        }
    }
}

2. 将UIStoryBoardSegueWithCompletion设置为您的退出segue类

注意:此segue的操作应与原始问题匹配为unwindToMainMenu

从storyboard中选择exit segue 添加自定义类

3. 更新您的unwind @IBAction以执行完成处理程序中的代码

@IBAction func unwindToMainMenu(segue: UIStoryboardSegue) {
    if let segue = segue as? UIStoryboardSegueWithCompletion {
        segue.completion = { 
            self.performSegueWithIdentifier("Categories", sender: self) 
        }
    }
}

当退出转场完成其过渡后,您的代码将立即执行


4
我只能通过将完成操作包装在转场协调器中才能保证它的可靠性:`destinationViewController.transitionCoordinator()?.animateAlongsideTransition(nil, completion: { _ in completion() })` - Bringo
@Bringo 这很有趣...如果您正在设置转换协调器,那么您可以直接从取消操作设置转换协调器的完成块,而无需通过自定义segue。 - wyu
好的,你不能这样做,因为在取消展开转场调用期间没有活动转场,因此转场协调器为nil(即转场发生在取消展开转场调用返回之后)。 - wyu
这会导致我的应用程序出现内存泄漏!:( - Thomás Pereira
你可能想在segue完成时使用weak self。如果这样可以解决你的内存泄漏问题,请告诉我,我可以更新答案@TomCalmon。 - wyu
不,那并没有解决问题。我最终设置了一些标志,并在viewDidDisappear(animated:)中执行代码。 - Thomás Pereira

12

目前我希望提供自己的解决方案来解决这个问题。欢迎随时提供更多答案。

我在 MainPageViewController 中放置了一个布尔变量和 viewDidAppear 函数。

var fromCamera = false

override func viewDidAppear(animated: Bool) {
    if fromCamera {
        self.performSegueWithIdentifier("categorySelection", sender: self)
        self.fromCamera = false
    }
}

在从 CropViewController 执行解除 unwind segue 之前,我将 fromCamera 设置为 true。通过这种方式,只有在从裁剪视图执行解除 unwind segue 时,我才会执行到分类屏幕的 segue。


更可靠的解决方案。但不幸的是,当从模态呈现的视图中解开时,我不得不采用hackish解决方案,因为在这种情况下viewDidAppear不会被调用。 - mohonish

6

继承 UIStoryBoardSegue 类(参考这个答案,该答案只提供了 Objective-C 代码)

#import <UIKit/UIKit.h>

@interface MyStoryboardSegue : UIStoryboardSegue

/**
 This block is called after completion of animations scheduled by @p self.
 */
@property (nonatomic, copy) void(^completion)();

@end

在动画完成后调用此完成块。

@implementation MyStoryboardSegue

- (void)perform {
  [super perform];
  if (self.completion != nil) {
    [self.destinationViewController.transitionCoordinator
     animateAlongsideTransition:nil
     completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
       if (![context isCancelled]) {
         self.completion();
       }
     }];
  }
}

@end

5
继续前两个回答,这里有关于Objective-C版本的更多细节(我也只有Objective-C代码)。
  1. Subclass UIStoryboardSegue with UIStoryboardSegueWithCompletion

    class UIStoryboardSegueWithCompletion: UIStoryboardSegue { var completion: (() -> Void)?

    override func perform() {
        super.perform()
        if let completion = completion {
            completion()
        }
    }
    

    }

UIStoryboardSegueWithCompletion.h

#import <UIKit/UIKit.h>

@interface MyStoryboardSegue : UIStoryboardSegueWithCompletion

@property (nonatomic, copy) void(^completion)();

@end

UIStoryboardSegueWithCompletion.m

#import "UIStoryboardSegueWithCompletion.h"

@implementation UIStoryboardSegueWithCompletion

- (void)perform {
  [super perform];
  if (self.completion != nil) {
    [self.destinationViewController.transitionCoordinator
     animateAlongsideTransition:nil
     completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
       if (![context isCancelled]) {
         self.completion();
       }
     }];
  }
}
@end
  1. 将UIStoryBoardSegueWithCompletion设置为您的退出转场的类

注意:此转场的操作应该是unwindToMainMenu,以匹配原始问题 [显示转场 UI 的图像][1] [显示转场 UI 2 的图像][2]

从Storyboard中选择“退出segue”,添加自定义类

-(IBAction)unwindToMainMenu(UIStoryboardSegue *)segue {
    if([segue isKindOfClass:[UIStoryboardSegueWithCompletion class]]){
        UIStoryboardSegueWithCompletion *segtemp = segue;// local prevents warning
        segtemp.completion = ^{
            NSLog(@"segue completion");
            [self performSegueWithIdentifier:@"Categories" sender:self];
        };
    }
}

在退出转场完成其过渡后,您的代码将会执行


4

我猜测performSegue没有触发是因为未完成返回segue。目前我想到的唯一一件事就是使用dispatch_after延迟调用performSegue。但这对我来说似乎非常“hacky”。

@IBAction func unwindToMainMenu(segue: UIStoryboardSegue) {
    dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
        self.performSegueWithIdentifier("Categories", sender: self)
    }
}

我还发现了一种可能也有些“hacky”的方法:我在unwind函数中设置一个布尔变量为true,在viewDidAppear函数中,如果该布尔变量为true,则调用performForSegue函数。您对这个解决方案有什么看法? - Berkan Ercan
1
我认为这是一个不错的解决方案。dispatch_after由于等待时间的变化可能会产生问题。使用布尔值和viewDidAppear确保performSegue在视图准备好时执行。 - Ron Fessler

3
退出转场的IBAction方法在实际的解除转场之前发生。我也遇到了同样的问题,并通过以下方式解决了它(如果你不介意我的代码改动)。这种方式避免了依赖于ViewDidAppear所需的额外时间和动画。
@IBAction func unwindToMainMenu(segue: UIStoryboardSegue) {
   let categoriesTable = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CategoryTableViewController")
   self.navigationController?.viewControllers.append(categoriesTable)
   self.navigationController?.showViewController(categoriesTable, sender: self)
}

希望这对于其他遇到这个问题并希望立即过渡的人有所帮助!

3

针对 Swift 5 对 @moride 的答案进行了更新。转换协调器现在是可选的,所以如果情况如此,我们会立即运行完成处理。

class UIStoryboardSegueWithCompletion: UIStoryboardSegue {
    var completion: (() -> Void)?

    override func perform() {
        super.perform()

        guard let completion = completion else { return }
        guard let coordinator = destination.transitionCoordinator else {
            completion()
            return
        }

        coordinator.animate(alongsideTransition: nil) { context in
            guard !context.isCancelled else { return }
            completion()
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接