Cocoa-window

Application的结构

在AppDelegate文件中获取当前appDelegate和app对象

AppDelegate *appDelegate = (AppDelegate *)[NSApplication.sharedApplication delegate];
NSApplication *app = NSApplication.sharedApplication.mainWindow;

让appDelegate不加载MainMenu中的window

只能删掉MainMenu或者其中的Window,因为无论怎么样都会去加载

window的结构

Cocoa-window

Cocoa-window
Cocoa-window

; NSScreen

screen存在mainScreen

cout

(int)[[NSScreen screens] count];

bound

相当于内部坐标
也是下面两个坐标

frame

这个是相对于superView来说的
frame有两个属性
一个是size:width,height
一个是origin:x,y就是原点

[[NSScreen mainScreen] frame]];//直接获取主屏的frame
NSScreen* screen = [[NSScreen screens] objectAtIndex:i];//获取索引为i的sreen的frame

[windowController.window setFrame:screenFrame display:YES];//将指定的screenFrame绑定为wc所绑定的window的frame 就会将这个window创建在这个screen上

NSWindow

activeWindow

就是左上角最大化,关闭,最小化这三个按钮是亮着的window,activeWindow必须是顶层窗口

foucedWindow

拥有键盘输入的窗口

foregroundWindow

一般是活动窗口,但是当一个窗口处于ALWAYS ON TOP时 活动窗口可能是别的但Always on top的窗口是foreground window.

使用 init 方法会默认调用 initWithNibName 方法去寻找 xib文件。

NSWindowController(wc)

一般我们要获取window的一些参数,或者想让window做什么事情
一般就是通过wc.window来获取 一般想有多个屏幕的时候就创建一个wcList = [NSMutableArray array];来存储创建的多个wc
是一个将窗口显示在屏幕上的控制器
NSWindowController里面会包含一个 NSWindow* window 这个window就是他控制的那个window
然后这个window需要一个NSViewController来管理具体显示的视图
然后wc通过调用showWindow来显示这个窗口

wc必须持久存在 否则可能会导致闪退的问题,所以我们要确保WindowController时刻存在一个引用,这样它才不会被ARC回收掉,那么最好的办法就是让他成为一个成员变量

创建窗口

一般是在wc的init方法里面调用 self = [super initWithWindowNibName:@"WindowController"];//这个里面是填那个xib文件的名字
例如

-(id)initwc
{
     self = [super initWithWindowNibName:@"WindowController"];
     if( !self )
        return nil;
     return self;
}

需要显示窗口就需要调用[self.window showWindow:nill];之后会调用initWithContentRect 然后再调用windowDidLoad
initWithContentRect 和windowDidLoad 只会在第一次显示window的时候调用

initWithWindowNibName:(NSString*)string

NSWindowController 将使用这个string搜索应用程序包中的 NIB 文件,并加载其中的窗口对象

wc里面的一些方法 我们可以自己重写

当window被创建的时候会调用-windowWillLoad, then -loadWindow, then -windowDidLoad.

你可以重写一个自己的Window类继承NSWindow,然后将这个window给wc 然后在wc重写这些方法

windowDidLoad

如果window加载成功了 就会自动调用这个 应该是加载完成的时候调用 并不会一直调用
好像是调用了[self.window showWindow:nill];之后会调用initWithContentRect 然后再调用windowDidLoad
这里一般会设置一些颜色啊 背景啥的

- (void)windowDidLoad {
    [super windowDidLoad];
    if (self.windowDelegate)
        [self.window setDelegate:self.windowDelegate];
    [self.window setRestorable:NO];
    [self.window setBackgroundColor:[NSColor blackColor]];
    [self.window setLevel:NSPopUpMenuWindowLevel + 3];
    [self.window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
    [self.window makeKeyAndOrderFront:nil];
}

AppDelegate

约束已经定义了

@property (nullable, nonatomic, strong) UIWindow *window API_AVAILABLE(ios(5.0));

所以要指定custom window作为appDelegate的window就得自己定义一个 名字不能 比如mainWindow

@property (strong, readwrite, nonatomic) UIWindow *window;

然后更改这个self.window = xxx

control(控件)

control和view的主要区别在于,控件作为用户交互的主要接口,通过用户行为和发出的数据作出相应的反应,一般control作为cell的容器,而view一般作为cntrol的container

NSView

View的主要定义:在window中定义了一个区域,方便添加控件,绘制和处理数据
NSView的主要作用是在window中定义一个矩形区域用来绘制控件并且可以接受鼠标和键盘事件
NSView的属性里面也有NSWindow

创建view

控件一般都写在view中
Mac 中创建 NSViewController 不会自动创建 view,就像是 创建view 不会自动创建 layer 一样

loadView

initWithFrame

用于创建一个view对象的frame rectangle,但是调用了并没有将这个view插入到window的hierarchy

如果是IB中拖拽出来的customView就会自动调用initWithFrame
如果是自己写的 这个需要去主动调用 并不会自动调用

一般可以通过重写initWithFrame来在创建view的时候初始化view的一些property(such as backGround coloer,color,location)
代码添加customView

NSView* customView = [[NSView alloc] initWithFrame:NSMakeRect(x,y,len,wigh)];
[self.contentView addSubview:customView];

也可以手动在xib里面移出来

viewDidLoad

只会在View第一次被加载的时候调用一次
关于这个还需要理解:viewDidLoad 此方法只有当view从nib文件初始化的时候才被调用。
loadView 此方法在控制器的view为nil的时候被调用。 此方法用于以编程的方式创建view的时候用

viewWillAppear

view将要显示在屏幕之前
因为viewDidload只会在第一次被加载的时候被调用 所以如果一个view被hidden之后 想要在他显示之前改变它进行数据更新 就必须在这个方法内实现

viewDidUnload

在发生内存警告的时候如果本视图不是当前屏幕上正在显示的视图的话, viewDidUnload将会被执行,本视图的所有子视图将被销毁,以释放内存,此时开发者需要手动对viewLoad、viewDidLoad中创建的对象释放内存。 因为当这个视图再次显示在屏幕上的时候,viewLoad、viewDidLoad 再次被调用,以便再次构造视图。
但是系统只会释放内存,并不会释放对象的所有权,所以通常我们需要在这里将不需要在内存中保留的对象释放所有权,也就是将其指针置为nil。

SubView

当一个view作为subView添加到另一个view上的时候 会自动调用viewWillMoveToSuperview和viewWillMoveToWindow

调用顺序

awakeFromNib->initWithFrame->drawRect->applicationDidFinishLaunching

内部属性

tag

可以唯一标识一个NSView

Bounds

文档上说是返回原点和NSView对象的大小

Frame

主要是确定view的location and size

Bounds和Frame的区别

Frame是一个view对应superView的坐标
Bounds是一个view的内部坐标

window

会返回包含这个view的window

layer

一般要先调用 self.wantsLayer
layer和view的区别
view是画板,layer是画布
一个程序可以有很多块画板
一块画板可以有很多块画布
view是layer的外层封装,view注重内容管理,layer注重绘制

视图绘制

延迟绘制(默认)

通常app会标记一个view为requiring update 在event loop结束的时候或者responsed一个explicit display reuquest之后调用drawRect方法进行绘制 主要是为了优化性能 将一个loop中所有的request都放在一起来绘制也就是下面这两个方法
setNeedsDisplay:和setNeedsDisplayInRect:会使当前视图或rect定义的区域变为invalidate状态 系统并不会立即绘制 而是在下一个绘图周期进行绘制

立即绘制

如果想立刻绘制 则让NSView或者NSWindow 调用display҅:和displayRect:则会强制视图立即进行绘制

默认是在drawRect函数中绘制视图 如果要在drawRect之外绘制 则需要使用[self.layerView lockFocus]锁定视图 绘制完成用 然后使用[self.layerView unlockFocus]解锁 如果在执行lockFocus时已经有其他的流程执行了lockFocus则会将当前操作保存到队列中 等待其他流程执行unlockFocus来恢复再执行

drawRect

属于NSView 会被自动调用 也可以override
如果你调用[view setNeedsDisplay] 会对这个view进行标记 系统会在下一轮对这个view重新绘制 也就是调用drawRect

updateLayer

getRectsBeingDrawn:count:和needsToDrawRect:

进一步约束了绘制 分别提供红了对视图无效区域的详细表示的直接和间接访问
可以在drawRect中调用getRectsBeingDrawn:count: 以缩影定义视图要绘制的区域的非重叠矩形列表,然后 它可以便利这个矩形列表 对其内容执行交集测试 以确定实际要绘制的内容

Non-Quartz Graphic Enviroments

应该是跟绘图环境有关 具体还不清楚

view hierarchy

就是view有层次的链接在一起
view hierarchy应该被视作一个Cocoa对象的集合,所以当一个view从view hierarchy中remove(removeFromSuperview),应该理解为一个对象从一个集合中移除,所以理所当然这个对象会被release,如果是添加进来 addSubView自然会对这个view对象做一次retain操作

view绘制顺序

当一个view接受到一个display request他会先绘制自己 然后他会依次passes drawing responsibility给每一个他的subviews 这个绘制会在下一个branch开始之前结束(就是子视图绘制之前,该视图一定完成了绘制) 应该意味着并不是reuqest发送就能完成所有绘制

contentView

window实例是维护最顶层view实例 也就是window.contentView实际上就是最顶层那个view
这个contentView就会作为一个window里面visible view hierarchy的root

Cocoa-window

NSWindow中的contentView 代表的是window的根视图

@property(strong)id contentView

设置contentView

这样可以在运行时动态的添加view

NSView *view = [[NSView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
[self.window.contentView addSubview:view];

当调用addSubView的时候会自动调用 viewWillMoveToSuperview和viewWillMoveToWindow:所以你可以重写这两个方法 使得在添加的时候可以查询window和superView的状态和更新自己的状态

正常来说 通过xib设计window元素布局的话 直接从控件库托上去就行了
但是在运行过程中要动态添加view元素到NSWindow 就需要使用contentView 这个代表的是window的根视图
有两种方法改变contentView
1.使用自定义的NSView或者NSViewController的view增加到contentView

[self.window.contentView addSubview:view];

2.创建一个NSViewController的子类 然、后复制给NSWindow的contentViewController

NSViewController *myVC = [[NSViewController alloc]init];
self.window.contentViewController = myVC;
@property (nonatomic, retain) IBOutlet NSView *contentView;

每个 window 用 contentView 属性持有一个指向其顶层 view 的引用。

把窗口的 contentView 的 wantsLayer 属性设置为 YES 是启用 layer backing 最简单的方法。这会导致 window 的视图树中所有的 view 都启用 layer backing,这样就没必要反复设置每个 view 的 wantsLayer 属性了

size和position

当一个view resize后 resize 他的subView之前会默认的广播 notification给对这个感兴趣的observers 告诉他们这个view的bounds 或者 frame rectangles改变了 这个notification是NSViewFrameDidChangeNotification,NSViewBoundsDidChangeNotification
NSViewHeightSizable 可以通过setPostsFrameChangeNotification:NO,setPostsBoundsChangeNotification:NO

If set, the view’s height changes proportionally to the change in the superview’s height. Otherwise, the view’s height does not change relative to the superview’s height.

NSViewWidthSizable

If set, the view’s width changes proportionally to the change in the superview’s width. Otherwise, the view’s width does not change relative to the superview’s width.

NSViewMinXMargin

If set, the view’s left edge is repositioned proportionally to the change in the superview’s width. Otherwise, the view’s left edge remains in the same position relative to the superview’s left edge.

NSViewMaxXMargin

If set, the view’s right edge is repositioned proportionally to the change in the superview’s width. Otherwise, the view’s right edge remains in the same position relative to the superview.

NSViewMinYMargin

If set and the superview is not flipped, the view’s top edge is repositioned proportionally to the change in the superview’s height. Otherwise, the view’s top edge remains in the same position relative to the superview.

If set and the superview is flipped, the view’s bottom edge is repositioned proportionally to the change in the superview’s height. Otherwise, the view’s bottom edge remains in the same position relative to the superview.

NSViewMaxYMargin

If set and the superview is not flipped, the view’s bottom edge is repositioned proportional to the change in the superview’s height. Otherwise, the view’s bottom edge remains in the same position relative to the superview.

If set and the superview is flipped, the view’s top edge is repositioned proportional to the change in the superview’s height. Otherwise, the view’s top edge remains in the same position relative to the superview.

Cocoa-window

; 注意

处于性能考虑 cocoa并不会在同级view之间进行强制裁剪,也并不能保证两个同级视图重叠时显示正确 所以你想让a视图显示在b视图的前面 你需要让a视图成为b视图的subView

如果调用了removeFromSuperview就会自动的released

resize
repositioning可能让superView原先不可见的部分变得可见 所以需要重新绘制
autoReize的view必须完全位于superView内 并且如果view发生了旋转 则这个view的subview都不能实现autoResize了
要去查一下layer-backed view是什么

如果有关于view失效或者重叠的问题看这里

apple官方文档

acceptsFirstResponder

NSViewController

windowController.window.contentViewController = [[NSViewWindowController alloc]init]
窗口显示什么东西由viewController说了算
NSViewController 的内容由一个个的 NSView 决定。

AVCaptureVideoPreviewLayer

应该是主要拿到screen上面捕获到一帧一帧数据

整体结构


@interface WindowController : NSWindowController
{
}
@property (nullable, strong) window *window;
@property (nonatomic, retain) IBOutlet NSView *contentView;
@property (nonatomic, readwrite, retain) WindowDelegate* windowDelegate;
@end

@interface Window : NSWindow
{
}
@property (nonatomic, retain) IBOutlet NSView *contentView;
@property (nonatomic, retain) IBOutlet NSViewController *contentViewController;
@end

@interface contentViewController : NSViewController
{
}
@property (nonatomic, retain) IBOutlet NSView *View;
@end

流程


NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable ;
NSWindow *mWindow = [[NSWindow alloc]initWithContentRect:CGRectMake(0, 0, 400, 200) styleMask:style backing:NSBackingStoreBuffered defer:YES];

wc = [[mWindow alloc]initWithWindow:mWindow];

NSViewController *vc = [[NSViewController] alloc] init];

wc.mWindow.contentViewController = vc;

NSView *mView = [[NSView alloc]initWithFrame:CGRectMake(0, 0, 200, 100)];

view.wantsLayer = YES;
view.layer.backgroundColor = [NSColor yellowColor].CGColor;

vc.view = mView;

逻辑关系

window.contentView == window.contentViewController.view ==  windowController.window.contentViewController.view

xib文件

xib文件在编译后就是nib文件

storyboard(好像swift才有)

关于storyboard的建议,其实在做macOS开发的时候,storyboard并不好用,不像在iOS开发时那样得心应手,所以还是建议把视图的设计用xib,然后关于控制器的部分用代码来书写。但是也并不建议直接把storyboard删掉,因为用它来编辑状态栏的下拉菜单还是非常方便地,所以本人的做法是在storyboard中只留一个file menu,把其他的视图和控制器都删除掉。当然这样的话,在项目设置处的入口storyboard就必须还得是Main才行。

如何在ViewController中,以新Window的方式显示某个View?

present就是能以新Window显示View的

 [self presentViewControllerAsSheet:self.subViewCtl];

好像这个也可以presentViewControllerAsModalWindow

适时调整大小(live resize)

inLiveResize决定一个view是否进行适时调整大小,如果这个视图是live resize 则会尽可能少的绘制它同时保证这个view具有你所需的外观

方法

setNeedsDisplayInRect

setNeedsDisplayInRect:会使当前视图或rect定义的区域变为invalidate状态 系统并不会立即绘制 而是在下一个绘图周期进行绘制

setNeedsDisplay

setNeedsDisplay:会使当前视图或rect定义的区域变为invalidate状态 系统并不会立即绘制 而是在下一个绘图周期进行绘制

注意

一个windowController如果自带了一个xib文件,那这个windowController只会

appDelegate.m为什么不需要调用initWithWindowNibName就可以加载MainMenu.xib中的window

因为 AppDelegate 类并不是 NSWindowController 的子类,而是 NSApplication 的代理类。NSApplication 类在运行时会自动加载名为 “MainMenu” 的 NIB 文件,并从其中创建一个窗口对象,作为应用程序的主窗口。
当应用程序启动时,NSApplication 会首先调用代理类的 applicationDidFinishLaunching: 方法。在此方法中,代理可以执行其他必要的初始化操作,并将 NSWindow 对象设置为应用程序的主窗口

NSWindowCollectionBehaviorCanJoinAllSpaces和NSWindowCollectionBehaviorMoveToActiveSpace的区别

NSWindowCollectionBehaviorCanJoinAllSpaces是一个窗口级别的属性,用于指定窗口是否可以加入所有空间,如果设置了这个属性,则该窗口会显示在所有空间中。
NSWindowCollectionBehaviorMoveToActiveSpace是一个应用程序级别的属性,用于指定应用程序是否应该将其所有窗口移动到当前活动空间,如果设置了这个属性,则应用程序的所有窗口都会自动移动到新的空间。
如果window1选择的是NSWindowCollectionBehaviorMoveToActiveSpace,在你移动workSpace的时候 如果目标workSpace已经存在active window那window1就不会在目标workSpace显示,自动隐藏。但是如果这个window有个相关的window的level更高就可能会把这个window1带出来(具体还要验证一下)
如果是NSWindowCollectionBehaviorCanJoinAllSpaces就不会管你是否有activeWindow,都会显示出来,如果level更低就会显示在后面,

保持某个窗口一直显示在wrokSpace上

就是当wrokSpace切换(切换桌面的时候)保证某个窗口一直显示在当前切换的桌面上


    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(changeActiveSpace:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];

- (void)changeActiveSpace:(NSNotification *)sender
{
    NSLog(@"调用了");
    [self.mainWindow close];
    [self.mainWindow makeKeyAndOrderFront:nil];
}

让一个窗口显示在另一个全屏应用(专注模式)之上

[self.mainWindow setStyleMask:NSWindowStyleMaskBorderless | NSWindowStyleMaskNonactivatingPanel];

Original: https://blog.csdn.net/qq_43535469/article/details/127823832
Author: 彭同学她同桌
Title: Cocoa-window

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/660668/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球