【问题标题】:NSContainerView with multiple child view controllers in Xcode 6Xcode 6 中带有多个子视图控制器的 NSContainerView
【发布时间】:2023-03-12 03:00:02
【问题描述】:

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

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

谁能告诉我这是如何使用 Xcode 6 实现的?

【问题讨论】:

  • 我不知道您是否出于其他原因需要 ContainerView,但如果您只想要双 ViewController 功能,您可以采用不同的方法。在 Storyboard 中创建您的第一个“ViewController”视图;将其分配为自定义类;调整您需要的任何组件的大小,为第二个 ViewController 留出空间。然后在第一个 VC 的代码中,将第二个 VC 加载到您留下的空间中。如果这种方法有帮助,我可以提供代码 sn-ps 吗?

标签: macos cocoa xcode6 nsviewcontroller


【解决方案1】:

首先,这是解决方案的示例 GitHub 项目:click。我不确定您是想交换视图还是简单地将第二个视图推送到众所周知的堆栈中,所以我采用了推送/弹出方案。如果你想交换视图,你应该可以通过跳过堆栈存储来相当容易地做到这一点。

本质上,我们有我们的“主机”NSViewController,其中包含一个容器视图(CV)。该主机实际上并没有手动管理 CV 目前显示的视图控制器。这样做的方式是通过一种嵌套的视图控制器,然后管理您将要显示/隐藏/推送/弹出/交换/等的所有其他视图控制器。 (注意:您可能可以稍微移除分层,但在 iOS 术语中,我将情节提要屏幕截图中的“子视图控制器管理器”视为UINavigationController)。

我们还利用了一些自定义转场/转场动画师,以便能够在情节提要中完成更多工作。

您只需要告诉内容视图管理器视图控制器以这样的方式操作其子视图,即保留您想要“返回”到的旧视图(在这种情况下,使用NSMutableArray)等新视图具有正确的frame 或正确设置了它们的约束。

这是情节提要的屏幕截图: 您在情节提要上看到的自定义类型(如下所示 -> { } )的每个 segue 都是示例项目中的 SegueBetweenEmbedded 类型。按下按钮执行转场,标记为“Pop”的按钮在NSViewController 上执行dismissController:(这在情节提要中完成)。

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

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(对嵌套类不抱歉)

#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

【讨论】:

  • 我发现它很有用,谢谢,但是在 swift 中需要类似的东西。如果有人需要它 - 给你:github.com/Ruzard/ContainerSequencedView 如果你提供了如何使用快速技术改进它 - 我将不胜感激。由于没有时间优化,几乎逐行重写
  • 我检查了你关于故事板的截图。如何处理放大 macOS 项目的情节提要?我发现缩放/缩放不可用。我认为这是错误。是吗?
  • @mcsh0k 您无法放大 macOS 项目的情节提要。我在我的 Mac 上截取了故事板那部分的屏幕截图,然后将其发布在这里。我不太明白您的评论是否与提议的解决方案有关;你能帮我了解一下可能是什么错误吗?
猜你喜欢
  • 1970-01-01
  • 2014-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-05
  • 2011-10-10
相关资源
最近更新 更多