MacOS 开发 - NSSplitView & NSSplitViewController


一、简述

1、相关类

  • NSSplitView
  • NSSplitViewItem
  • NSSplitViewController

进入各个类头文件可以发现,NSSplitViewController 继承自 NSViewController,主要管理 NSSplitView 和 NSSplitViewItem;
NSSplitViewItem 可以管理一个 NSViewController;
NSSplitView 可以管理一组 NSView;只有 NSSplitView 有代理方法 NSSplitViewDelegate。

个人认为,简单的视图可以只是用 NSSplitView;
复杂的视图,是用 NSSplitViewController 来控制更好。


2、相关名词

  • divider 分隔条
  • ‘ŽŽƒ’•‡‘ŽŽƒ’•‡collapse 折叠
  • reveal 显示
  • proposedPosition 拖拽中的位置

二、NSSplitView 使用

1、创建

- (void)addSplitView{

    //1、添加 splitView
    NSSplitView *splitView = [[NSSplitView alloc]initWithFrame:NSMakeRect(20, 20, 100, 130)];
    [self.window.contentView addSubview:splitView];
    
    //设置背景色
    splitView.wantsLayer = YES;
    splitView.layer.backgroundColor = [NSColor cyanColor].CGColor;
    
    //分割方向:垂直/水平
    [splitView setVertical:YES];
    
    //分割区域的样式
    [splitView setDividerStyle:NSSplitViewDividerStyleThin];
    
    //2、添加子视图
    NSView *leftView = [[NSView alloc]initWithFrame:NSZeroRect];
    leftView.autoresizingMask = 0;
    [leftView setAutoresizesSubviews:YES];
    
    NSView *rightView = [[NSView alloc]initWithFrame:NSZeroRect];
    rightView.autoresizingMask = 0;
    [rightView setAutoresizesSubviews:YES];
    
    [splitView addSubview:leftView];
    [splitView addSubview:rightView];
    
    
    [splitView setAutoresizesSubviews:YES];
    [splitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
    }
    

1.1 vertical:上下 or 左右分割

vertical


1.2 NSSplitViewDividerStyle:分割线样式
typedef NS_ENUM(NSInteger, NSSplitViewDividerStyle) {
    NSSplitViewDividerStyleThick = 1,
    NSSplitViewDividerStyleThin = 2,
    NSSplitViewDividerStylePaneSplitter NS_ENUM_AVAILABLE_MAC(10_6) = 3,
} NS_ENUM_AVAILABLE_MAC(10_5);

效果、差异如图:

  • 三个都有背景色

NSSplitViewDividerStyle - 三个都有背景色


  • split有背景色

NSSplitViewDividerStyle - split有背景色


  • 都没有背景色

NSSplitViewDividerStyle - 都没有背景色


2、创建多个视图

在上面的基础上,多加一个view就好

- (void)addSplitView{
    
    CGFloat splitW = 500;
    CGFloat splitH = 500;
    CGFloat leftW = 130;
    
    
    //1、添加 splitView
    NSSplitView *splitView = [[NSSplitView alloc]initWithFrame:NSMakeRect(20, 20, splitW, splitH)];
    [self.window.contentView addSubview:splitView];
    
    //设置背景色
    splitView.wantsLayer = YES;
    splitView.layer.backgroundColor = [NSColor cyanColor].CGColor;
    
    splitView.delegate = self;
    
    //分割方向:垂直/水平
    [splitView setVertical:YES];
    
    //分割区域的样式
    // NSSplitViewDividerStyleThin  NSSplitViewDividerStyleThick  NSSplitViewDividerStylePaneSplitter
    [splitView setDividerStyle:NSSplitViewDividerStylePaneSplitter];
    
    [splitView setAutoresizesSubviews:YES];
    [splitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
    
    //2、添加子视图
//    NSView *leftView = [[NSView alloc]initWithFrame:NSZeroRect];
    NSView *leftView = [[NSView alloc]initWithFrame:NSMakeRect(0, 0, leftW, splitH)];
    leftView.autoresizingMask = 0;
    [leftView setAutoresizesSubviews:YES];
    
    leftView.wantsLayer = YES;
    leftView.layer.backgroundColor = [NSColor orangeColor].CGColor;
    
//    NSView *rightView = [[NSView alloc]initWithFrame:NSZeroRect];
    NSView *rightView = [[NSView alloc]initWithFrame:NSMakeRect(130, 0, 100, splitH)];
    rightView.autoresizingMask = 0;
    [rightView setAutoresizesSubviews:YES];
    
    rightView.wantsLayer = YES;
    rightView.layer.backgroundColor = [NSColor yellowColor].CGColor;
    
    [splitView addSubview:leftView];
    [splitView addSubview:rightView];
    
    NSView *rightView1 = [[NSView alloc]initWithFrame:NSMakeRect(230, 0, splitW - leftW - 100, splitH)];
    rightView1.autoresizingMask = 0;
    [rightView1 setAutoresizesSubviews:YES];
    
    rightView1.wantsLayer = YES;
    rightView1.layer.backgroundColor = [NSColor redColor].CGColor;
    
    [splitView addSubview:rightView1];
}


效果

在这里插入图片描述


三、NSSplitViewDelegate

1、subView 尺寸设置


//最小
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex{

//    NSLog(@"-- %ld - %f",dividerIndex,proposedMinimumPosition);

    if (dividerIndex == 0) {
        return 100;
    }
    return proposedMinimumPosition;

}


// 最大
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex{

    NSLog(@"-- %ld , %f",(long)dividerIndex,proposedMaximumPosition);

    if (dividerIndex == 0) {
        return 1000;
    }
    return proposedMaximumPosition;
}



2、subView 尺寸变化

/*
 改变splitView 尺寸时,能否改变子试图的 size
 默认返回YES
 */
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view{
    
    NSLog(@"-- ");
    
    return NO;
}

- (void)splitViewWillResizeSubviews:(NSNotification *)notification{
    
//    NSLog(@"-- ");
}

- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize{
    
//        NSLog(@"-- ");
}


- (void)splitViewDidResizeSubviews:(NSNotification *)notification{
    
//    NSLog(@"-- ");
}



3、divider 相关

/*
 divider 的位置
 默认返回 constrainSplitPosition
 */
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex{

    NSLog(@"-- %ld , %f",(long)dividerIndex,proposedPosition);

//    if (dividerIndex == 1) {
//        return 120;
//    }

    return proposedPosition;
}

// 能否折叠(divider 能否移动)
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview{
    NSLog(@"-- ");
    return YES;
}

/*
 返回 YES 时,代表在 divider 上双击时, 子试图是否被折叠;将会先通知divider 前面的试图,然后通知后面的试图。
 当 -splitView:canCollapseSubview: 返回YES 才有效
 */
- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex {

    NSLog(@"-- ");
    if (dividerIndex == 0) {
        return YES;
    }
    return NO;
}

/*
 divider 的位置
 默认返回 constrainSplitPosition
 */
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex{
    
    NSLog(@"-- %ld , %f",(long)dividerIndex,proposedPosition);
    
    //    if (dividerIndex == 1) {
    //        return 120;
    //    }
    
    return proposedPosition;
}

/*
 是否隐藏 分隔条
 默认返回NO: 如果没有设置代理,或者设置了代理,没有实现这个方法,效果和使用了此方法 返回 NO 效果一致。
 */
- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex{

    NSLog(@"-- ");
    return NO;
//    return YES;
}

/*
 自定义 divider 的位置
 默认返回 proposedEffectiveRect
 */
- (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex{

    NSLog(@"-- proposedEffectiveRect : %@",NSStringFromRect(proposedEffectiveRect));  // PaneSplitter : {{258, 0}, {10, 500}}  , thick: {{130, 0}, {9, 500}} 
    return proposedEffectiveRect;
}

/*
 divider 之外其他区域,也可产生拖拽分离效果;
 如果没有响应这个方法,只有鼠标进入 divider 才能拖拽。
 如果有多个divider,但只返回一个rect;那么不管rect的x 值是多少,在rect 区域内拖动,都只对第一个diver 有效
*/

- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex{

    NSLog(@"-- ");

    return NSMakeRect(230, 0, 100, 500);
}


3.1 divider 位置与 constrainSplitPosition 方法的关系
divider 一开始显示在 leftView 右侧;
如果设置代理,响应 constrainSplitPosition 方法,后面拖拽以 constrainSplitPosition 返回的数值为准。
如果想要使用 constrainSplitPosition 方法,只指定某个 divider 的位置,可以当 dividerIndex 为特定值时返回 固定值,其余情况返回 proposedPosition。如下所示

- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex{

    NSLog(@"-- %ld , %f",(long)dividerIndex,proposedPosition);

    if (dividerIndex == 1) {
        return 120;
    }
    return proposedPosition;
}


4、其它

4.1、设置代理后,可能不显示 divider,控制台还会报一下提示

<NSSplitView: 0x10062de30>: the delegate <AppDelegate: 0x600000010230> was sent -splitView:resizeSubviewsWithOldSize: and left the arranged view frames in an inconsistent state:
 Split view bounds: {{0, 0}, {500, 500}}
     Arranged view frame: {{0, 0}, {0, 0}}
     Arranged view frame: {{0, 0}, {0, 0}}
 The outer edges of the arranged view frames are supposed to line up with the split view's bounds' edges. NSSplitView is working around the problem, perhaps at the cost of more redrawing. (This message is only logged once per NSSplitView.)
 

关键字在于 arranged view frames in an inconsistent state , 可以联想到是 frame 设置不对。 修改子视图的 frame,宽高加起来 和 父视图一致,就没有再报错了。

    CGFloat splitW = 500;
    CGFloat splitH = 500;
    CGFloat leftW = 130;
    
    
    //1、添加 splitView
    NSSplitView *splitView = [[NSSplitView alloc]initWithFrame:NSMakeRect(20, 20, splitW, splitH)];
    ...
    
    NSView *leftView = [[NSView alloc]initWithFrame:NSMakeRect(0, 0, leftW, splitH)];
    ...
    
    NSView *rightView = [[NSView alloc]initWithFrame:NSMakeRect(130, 0, splitW - leftW, splitH)];
    ...
    
    

四、NSSplitViewController 使用

可以猜想和 iOS 中的 UITableViewController 类似,都内置一个视图(UITableView),不用另外设置 delegate。
浴室创建控制器继承自 NSSplitViewController,并设置delegate 的 VC 为它。

1、基本创建


@interface OneSplitViewController : NSSplitViewController

@end
@implementation OneSplitViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.wantsLayer = YES;
    self.view.layer.backgroundColor = [NSColor cyanColor].CGColor;
    
    [self testNSSplitViewController];
    NSLog(@"%@",self.view);
}


- (void)testNSSplitViewController{
    
    FirstViewController *firstVC = [[FirstViewController alloc]initWithNibName:@"FirstViewController" bundle:nil];
    SecondViewController *secondVC = [[SecondViewController alloc]initWithNibName:@"SecondViewController" bundle:nil];
    
    NSSplitViewItem *item1 = [NSSplitViewItem splitViewItemWithViewController:firstVC];
    NSSplitViewItem *item2 = [NSSplitViewItem splitViewItemWithViewController:secondVC];

    [self addSplitViewItem:item1];
    [self addSplitViewItem:item2];
}



效果如下

在这里插入图片描述


2、设置 splitView

可以发现上面效果是没有 divider 的,这时也想到有个controller 的 splitView 属性还没用上。

.splitView 是不是 .view 呢? 打印下并不是

NSLog(@"view : %@ , splitView : %@",self.view,self.splitView);
// view : <NSView: 0x600000121040> , splitView : <NSSplitView: 0x600000120460>


添加以下代码,divider 又出现了, delegate 默认为自己

    self.splitView.wantsLayer = YES;
    self.splitView.layer.backgroundColor = [NSColor redColor].CGColor;

    [self.splitView setVertical:NO];
    
    [self.splitView setDividerStyle:NSSplitViewDividerStyleThick];

    NSLog(@"self : %@ , splitView.delegate : %@",self,self.splitView.delegate);  // self : <OneSplitViewController: 0x6000000e3780> , splitView.delegate : <OneSplitViewController: 0x6000000e3780>
    

效果如下

在这里插入图片描述


3、错误 SplitViewController's splitView is unable to use autolayout

参考:https://stackoverflow.com/questions/26982714/nssplitviewcontroller-in-osx-10-10-using-xcode-6

添加 delegate 的 下述方法后,会提示错误

splitView:constrainMinCoordinate:ofSubviewAt:
splitView:constrainMaxCoordinate:ofSubviewAt:
splitView:resizeSubviewsWithOldSize:
splitView:shouldAdjustSizeOfSubview:

*** Assertion failure in -[NSSplitView _splitViewUseConstraintBasedLayout], /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.60.100/AppKit.subproj/NSSplitView.m:4125

  SplitViewController's splitView is unable to use autolayout because the SplitViewController overrides an incompatible delegate method.
  

原因:

由于 NSSplitViewController 需要使用自动布局,所以这几个方法和 VC 不兼容,不要在 NSSplitViewController 的子类中使用它们。

Autolayout must be used with NSSplitViewController to properly control the layout of the child views and the animations of collapses and reveals.

e.g., Constraints can be used to setup whether a window should grow/shrink or stay the same size when showing and hiding a sidebar.

Auto Layout NSSplitView improvements
In 10.8, NSSplitView properly respects constraints applied to its subviews, such as their minimum view widths.

There are also new APIs for controlling the holding priorities, which determine both the NSLayoutPriority at which a split view holds its sizes and also which views change size if the split view itself grows or shrinks.
In order to take advantage of these improvements, you must NOT implement any of the following NSSplitViewDelegate methods:

  • splitView:constrainMinCoordinate:ofSubviewAt:
  • splitView:constrainMaxCoordinate:ofSubviewAt:
  • splitView:resizeSubviewsWithOldSize:
  • splitView:shouldAdjustSizeOfSubview:

These methods are incompatible with auto layout. You can typically achieve their effects and more with auto layout.

https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/#10_8AutoLayout


4、设置布局

上面创建时默认平分布局,如何设置布局呢?

第一个想到的简单的方法是:

    firstVC.view.frame = NSMakeRect(0, 0, 150, 300);
    secondVC.view.frame = NSMakeRect(150, 0, 350, 300);

但是并没有效果

是不是需要自动布局的原因?


这里使用 masonry 再操作一次

 
    [firstVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.greaterThanOrEqualTo(@100);
        make.width.lessThanOrEqualTo(@500);
    }];
    
    [secondVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.greaterThanOrEqualTo(@500);
        make.width.lessThanOrEqualTo(@1000);
    }];
    

嘿嘿,OK了

在这里插入图片描述


为了防止内容控制器的变化,我们也可以这么写:


    [item1.viewController.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.greaterThanOrEqualTo(@100);
        make.width.lessThanOrEqualTo(@500);
    }];
    
    [item2.viewController.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.greaterThanOrEqualTo(@500);
        make.width.lessThanOrEqualTo(@1000);
    }];
    

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI工程仔

请我喝杯伯爵奶茶~!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值