NSContainerView在Xcode 6中具有多个子视图控制器

Bad*_*Cat 4 macos cocoa nsviewcontroller xcode6

我正在尝试将Xcode 6故事板上的两个不同的视图控制器与链接起来,NSContainerView以便可以有条件地切换它们。不幸的是,这里的本教程没有任何帮助,因为自Xcode以来,情况似乎已发生变化。

因此,我有两个不同的视图控制器,默认情况下其中一个已加载到容器视图中,但是我希望能够以编程方式将第二个视图控制器加载到容器视图中。Xcode 6仅允许在我从一个拖动到另一个时创建嵌入Segues,所以并没有太大帮助。

有人可以告诉我Xcode 6是如何实现的吗?

Dea*_*kle 5

首先,这是该解决方案的示例GitHub项目:单击。我不确定是要交换视图还是将第二个视图推入一个众所周知的堆栈,所以我采用了推/弹出方案。如果您想交换视图,则只需跳过堆栈存储,就可以轻松地做到这一点。

本质上,我们有一个“主机” NSViewController,其中包含一个容器视图(CV)。该主机实际上并没有手动管理CV目前正在显示的视图控制器。好的方法是通过一种嵌套的视图控制器,然后管理要显示/隐藏/推送/ pop / swap / etc的所有其他视图控制器。(请注意:您也许可以删除一些分层,但是用iOS术语来说,我正在情节提要中将“ Sub View Controller Manager”当作情节提要截图中的一种UINavigationController)。

我们还利用了一些自定义的segues / segue动画师,以便能够在情节提要中做更多的工作。

您只需要告诉内容视图管理器视图控制器以某种方式操作其子视图,即可保留要弹出的“旧”旧视图(在这种情况下,使用NSMutableArray),并使新视图具有权利frame或适当设置约束条件。

这是情节提要的屏幕截图: 在此处输入图片说明您在自定义类型的情节提要上看到的每个segue都是SegueBetweenEmbedded示例项目中的类型。按下按钮会执行顺序,而标记为“ Pop”的按钮将在dismissController:上执行NSViewController(因此在情节提要中完成)。

这是一些代码(其中有很多,所以我建议改为看示例项目):

ViewController.h

#import <Cocoa/Cocoa.h>
#import "ContentManagerViewController.h"

@class ContentManagerViewController;

@protocol ContentManagerViewControllerHolder <NSObject>

-(ContentManagerViewController*)retreiveContentManagerController;

@end

@interface ViewController : NSViewController <ContentManagerViewControllerHolder>

@end
Run Code Online (Sandbox Code Playgroud)

ViewController.m

#import "ViewController.h"
#import "ContentManagerViewController.h"
#import "BackForwardViewController.h"

@interface ViewController ()

@property ContentManagerViewController *vcController;

-(IBAction)pushViewController:(id)sender;
-(IBAction)popViewController:(id)sender;
-(IBAction)popToRootViewController:(id)sender;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

-(void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
    if ([[segue destinationController] class] == [ContentManagerViewController class]) {
        self.vcController = segue.destinationController;
    }
}

-(ContentManagerViewController*)retreiveContentManagerController {
    return self.vcController;
}

-(IBAction)pushViewController:(id)sender {
    // note: this works, but then pop is broken via dismissController: since it wasn't done with a segue.
    // Better way is to rig up a manual segue and execute the segue.
    //BackForwardViewController *viewController = [[NSStoryboard storyboardWithName:@"Main" bundle:nil] instantiateControllerWithIdentifier:@"BackForwardStoryboardID"];
    //[self.vcController push:viewController];

    [self performSegueWithIdentifier:@"CustomSegueToBackForward" sender:self];
}

-(IBAction)popViewController:(id)sender {
    [self.vcController pop];
}

-(IBAction)popToRootViewController:(id)sender {
    [self.vcController popToRoot];
}

@end
Run Code Online (Sandbox Code Playgroud)

SegueBetweenEmbedded.h

#import <Cocoa/Cocoa.h>

@interface SegueBetweenEmbedded : NSStoryboardSegue

@end
Run Code Online (Sandbox Code Playgroud)

SegueBetweenEmbedded.m(对不起嵌套类,对不起)

#import "SegueBetweenEmbedded.h"
#import "ContentManagerViewController.h"
#import "ViewController.h"

@interface SegueAnimator : NSObject <NSViewControllerPresentationAnimator>

- (void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController;
- (void)animateDismissalOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController;

@end

@implementation SegueAnimator

- (void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
    NSViewController *parent = [fromViewController parentViewController];
    if (parent && [parent class] == [ContentManagerViewController class]) {
        ContentManagerViewController *manager = (ContentManagerViewController*)parent;
        [manager push:viewController];
    }
    else if ([fromViewController conformsToProtocol:@protocol(ContentManagerViewControllerHolder)]) {
        id<ContentManagerViewControllerHolder> holder = (id<ContentManagerViewControllerHolder>)fromViewController;
        [[holder retreiveContentManagerController] push:viewController];
    }
}

- (void)animateDismissalOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
    NSViewController *parent = [viewController parentViewController];
    if ([parent class] == [ContentManagerViewController class]) {
        ContentManagerViewController *manager = (ContentManagerViewController*)parent;
        [manager pop];
    }
}

@end

@implementation SegueBetweenEmbedded

- (void)perform {
    SegueAnimator *animator = [[SegueAnimator alloc] init];
    [self.sourceController presentViewController:self.destinationController
                                        animator:(id<NSViewControllerPresentationAnimator>)animator];
}

@end
Run Code Online (Sandbox Code Playgroud)

ContentManagerViewController.h

#import <Cocoa/Cocoa.h>

@interface ContentManagerViewController : NSViewController

-(void)push:(NSViewController*)viewController;
-(void)pop;
-(void)popToRoot;

@end
Run Code Online (Sandbox Code Playgroud)

ContentManagerViewController.m

#import "ContentManagerViewController.h"
#import "BackForwardViewController.h"

@interface ContentManagerViewController ()

@property (weak) IBOutlet NSView *subViewControllerManager;

@property NSViewController *currentViewController;
@property NSMutableArray<NSViewController*> *viewControllerStack;

@end

@implementation ContentManagerViewController

-(instancetype)init {
    self = [super init];
    self.viewControllerStack = [NSMutableArray array];
    return self;
}

-(instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    self.viewControllerStack = [NSMutableArray array];
    return self;
}

-(instancetype)initWithNibName:(NSNibName)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    self.viewControllerStack = [NSMutableArray array];
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)showViewController:(NSViewController*)viewController {
    [self addChildViewController:viewController];
    viewController.view.frame = self.currentViewController.view.frame;
    [self.view addSubview:viewController.view];
    self.currentViewController = viewController;
}

-(void)removeCurrentViewControllerFromView {
    [self.currentViewController.view removeFromSuperview];
    [self.currentViewController removeFromParentViewController];
}

-(void)push:(NSViewController*)viewController {
    [self removeCurrentViewControllerFromView];
    [self.viewControllerStack addObject:viewController];
    [self showViewController:viewController];
}

-(void)pop {
    if (self.viewControllerStack.count > 1) {
        [self removeCurrentViewControllerFromView];
        [self.viewControllerStack removeLastObject];
        NSViewController *viewController = [self.viewControllerStack lastObject];
        [self showViewController:viewController];
    }
}

-(void)popToRoot {
    while (self.viewControllerStack.count > 1) {
        [self pop];
    }
}

-(void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
    // this will be called on the initial embed to set up the first view controller
    self.currentViewController = segue.destinationController;
    [self.viewControllerStack addObject:segue.destinationController];
}

@end
Run Code Online (Sandbox Code Playgroud)

BackForwardViewController.h

#import <Cocoa/Cocoa.h>

@interface BackForwardViewController : NSViewController

@end
Run Code Online (Sandbox Code Playgroud)

BackForwardViewController.m

#import "BackForwardViewController.h"

@interface BackForwardViewController ()

@end

@implementation BackForwardViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do view setup here.
}

@end
Run Code Online (Sandbox Code Playgroud)