NSContainerView with multiple child view controllers in Xcode 6

I am trying to link two different view controllers in an Xcode 6 storyboard NSContainerView

so that they can switch conditionally. Unfortunately this tutorial won't help here as things seem to have changed in Xcode.

So, I have two different view controllers and one of them is loaded into the container's view by default, but I want the program to be able to load the second view controller into the container. Xcode 6 allows embeddable Segues to be created when I drag and drop them from one to the other so it doesn't help.

Can someone tell me how this is achieved with Xcode 6?

+3


source to share


1 answer


First, here's a sample GitHub project solution: click . I wasn't sure if you wanted to swap views or just push the 2nd view onto the proverbial stack, so I went with a push / pop program. If you want to swap places instead, you can do it quite easily by simply skipping the stack store.

Essentially, we have our NSViewController "host" which contains a Container View (CV) inside it. This host does not actually manually manage the view controller that is showing the CV at the moment. The way it is done is something like a nested view controller that then manages all the other view controllers that you are going to show / hide / push / pop / swap / etc. (Note: you might be able to remove the layers a bit, but in iOS terms, I treat the "Sub View Dispatcher" in the storyboard screenshot as a UINavigationController

).

We are also using some custom segues / segue animators to be able to do more work in the storyboard.

You just need to tell the content view manager's view manager to manage its children in such a way that the old views you want to put back are preserved (in this case, using NSMutableArray

) and such that the new views are eligible frame

or their constraints are set correctly.

Here is a screenshot of the storyboard: the enter image description heresegue you see on the custom type storyboard (looks like this:> {}) is of the type SegueBetweenEmbedded

in the sample project. The buttons that press the execute button and the buttons labeled "Pop" execute dismissController:

on NSViewController

(as was done in the storyboard).

Here's some code (and there's a lot of it out there, so I suggest looking at an example project instead):

ViewController.h

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

@class ContentManagerViewController;

@protocol ContentManagerViewControllerHolder <NSObject>

-(ContentManagerViewController*)retreiveContentManagerController;

@end

@interface ViewController : NSViewController <ContentManagerViewControllerHolder>

@end

      

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

      

SegueBetweenEmbedded.h



#import <Cocoa/Cocoa.h>

@interface SegueBetweenEmbedded : NSStoryboardSegue

@end

      

SegueBetweenEmbedded.m (sorry, won't regret the nested class)

#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

      

ContentManagerViewController.h

#import <Cocoa/Cocoa.h>

@interface ContentManagerViewController : NSViewController

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

@end

      

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

      

BackForwardViewController.h

#import <Cocoa/Cocoa.h>

@interface BackForwardViewController : NSViewController

@end

      

BackForwardViewController.m

#import "BackForwardViewController.h"

@interface BackForwardViewController ()

@end

@implementation BackForwardViewController

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

@end

      

+2


source







All Articles