系统学习MacOS应用开发,但是细小的知识点容易忘记,因此在此做下笔记,说不定后续有时间回顾下!
--deep
,否则无法打包发布到App Store。某些场景需要限制用户必须处理完当前窗口的任务,此时需要 用到模态。模态的方式有两种:
1.Modal Window:霸道,其他窗口无法接受任何系统事件
[[NSApplication sharedApplication] runModalForWindow:self.strongWindow];
注意:结束模态窗口时,直接关闭窗口的方式模态一直存在,正确的做法:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:nil];
- (void)windowWillClose:(NSNotification *)notify {
[[NSApplication sharedApplication] stopModal];
}
NSModalSession sessionCode = [[NSApplication sharedApplication] beginModalSessionForWindow:window];
- (void)windowWillClose:(NSNotification *)notify {
if (sessionCode != 0) {
[[NSApplication sharedApplication] endModalSession:sessionCode];
}
}
总结:
1.任何窗口的关闭要么通过左上角关闭按钮,要么通过执行window的close方法关闭。
2.对于任何一种模态窗口,关闭后还要额外调用结束模态方法结束状态,若不执行,则其他窗口无法正常工作。
@property(nonatomic, strong) NSWindow *myWindow;
- (NSWindow *)window {
if (!_window) {
NSRect frame = NSMakeRect(0, 0, 200, 200);
NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
_window = [[NSWindow alloc] initWithContentRect:frame styleMask:????style backing:NSBackingStoreBuffered defer:YES];
_window.title = @"New Create Window";
}
return _window;
}
- (void)showWindowAction:(id)sender {
[self.window makeKeyAndOrderFront:nil];
[self.window center];
}
注意:通过上述代码创建的窗口只能显示和关闭一次,后续会崩溃!后续章节有详细分析。
窗口通知
当多窗口切换,两个窗口之间界面修改会影响另一个的数据模型时,通过监听NSWindowDidBecomeMainNotification
和NSWindowDidBecomeKeyNotification
重新获取数据刷新界面。
如何给NSWindow的contentView添加元素?
[self.window.contentView addSubview:view];
NSViewController *myVc = [[NSViewController alloc] init];
self.window.contentViewController = myVc;
- (void)setWindowTitleAndIcon {
[self.window setRepresentedURL:[NSURL URLWithString:@"/Users/zhuzhanlong/Desktop/image.jpeg"]];
[self.window setTitle:@"TestApp"];
[[self.window standardWindowButton:NSWindowDocumentIconButton] setImage:[NSImage imageNamed:@"help"]];
}
[self.window setOpaque:NO];
[self.window setBackgroundColor:[NSColor greenColor]];
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
另一种方法是监听NSWindowWillCloseNotification,并判断是mainWindow就关闭程序。
- (void)receiveWindowWillCloseNotification:(NSNotification *)notify {
NSWindow *window = notify.object;
if (window == self.mainWindow) {
[NSApp terminate:self];
}
}
applicationShouldTerminateAfterLastWindowClosed
方法返回NO或者未重写时,当窗口关闭时,点击Dock栏仍然可以恢复应用。代码如下:- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
[self.strongWindow makeKeyAndOrderFront:self];
return YES;
}
- (void)addButtonToTitleBar {
NSView *titleView = [self.window standardWindowButton:NSWindowCloseButton].superview;
NSButton *button = [[NSButton alloc] init];
button.title = @"Register";
float x = self.window.contentView.frame.size.width - 100;
button.frame = NSMakeRect(x, 0, 80, 24);
button.bezelStyle = NSBezelStyleRounded;
[titleView addSubview:button];
}
- (void)windowDidLoad {
[super windowDidLoad];
[self.window setRestorable:NO];
//居中
[self.window center];
//或者指定在屏幕的固定位置
NSRect frame = NSMakeRect(0, 0, 100, 100);
[self.window setFrame:frame display:YES];
}
坐标系统
macOS的坐标原点(0, 0)在XY轴的左下角
翻转坐标系,重写NSView子类的isFlipped属性,返回YES即可。
Frame和Bounds
老生常谈,Frame表示视图在父视图中的位置。Bounds是本身内部的坐标系统,bounds坐标原点的变化会影响子视图的位置。
理解视图Bounds的变化对子视图的影响是理解NSSCrollView的关键。
坐标转换
视图类提供了丰富的坐标转换方法,从视图到视图、视图到窗口、视图到屏幕绘制缓冲区、视图到Layer等。最常用的是鼠标事件处理中。
- (void)mouseDown:(NSEvent *)event {
//从窗口坐标转换为视图坐标
NSPoint clickedPoint = [self convertPoint:[event locationInWindow] fromView:nil];
}
- (void)viewWillMoveToSuperview:(NSView *)newSuperview {
}
- (void)viewDidMoveToSuperview {
}
视图查找
可以给视图提供一个Tag值,通过ViewWithTag深度遍历算法查找子视图。
视图Layer属性
由于NSView默认没有Layer属性,和UIView稍有不同,如果需要设置背景色、圆角等属性,需要声明Layer。
self.wantsLayer = YES;
self.layer.backgroundColor = [[NSColor redColor] CGColor];
self.layer.borderColor = [[NSColor greenColor] CGColor];
self.layer.borderWidth = 2;
self.layer.cornerRadius = 20;
//延迟绘制
[self.view needsDisplay];
[self.view setNeedsDisplay:YES];
//立即绘制
[self.view display];
[self.view displayRect:self.view.frame];
注意,在drawRect:方法之外绘制时,需要使用:
[self.view lockFocus];
[self.view unlockFocus];
- (void)saveSelfAsImage {
[self lockFocus];
NSImage *image = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:self.frame]];
[self unlockFocus];
NSData *imageData = [image TIFFRepresentation];
//创建文件并保存
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) lastObject];
path = [path stringByAppendingPathComponent:@"test.jpg"];
[[NSFileManager defaultManager] createFileAtPath:path contents:imageData attributes:nil];
//完成后Finder中打开
NSURL *fileUrl = [NSURL fileURLWithPath:path];
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[fileUrl]];
}
如果视图比较大,带滚动视图,则通过如下方法获取整个滚动视图的截图:
- (void)saveScrollViewAsImage {
[self lockFocus];
NSData *pdfData = [self dataWithPDFInsideRect:self.frame];
[self unlockFocus];
NSPDFImageRep *img = [NSPDFImageRep imageRepWithData:pdfData];
NSInteger count = [img pageCount];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) lastObject];
path = [path stringByAppendingPathComponent:@"test.png"];
for (int i = 0; i < count; i++) {
[img setCurrentPage:i];
NSImage *image = [[NSImage alloc] init];
[image addRepresentation:img];
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
NSData *finalData = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[[NSFileManager defaultManager] createFileAtPath:path contents:finalData attributes:nil];
}
//完成后Finder中打开
NSURL *fileUrl = [NSURL fileURLWithPath:path];
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[fileUrl]];
}
禁用某一方向的滚动,需要继承NSSCrollView并重写scrollWheel:方法,判断满足一定条件就返回。
@implementation DisableVerticalScrollView
- (void)scrollWheel:(NSEvent *)event {
float dy = fabs(event.deltaY);
if (event.deltaX == 0.0 && dy > 0.01) {
return;
}else if(event.deltaX == 0.0 && dy == 0.0) {
return;
}else {
[super scrollWheel:event];
}
}
绑定事件用IBAction,当用户在搜索框内实时输入时会执行事件,这样可做实时搜索。
此处强调,获取搜索框上的两个按钮及事件处理如下:
- (void)registerSearchActionButton {
NSActionCell *searchCell = [[self.searchField cell] searchButtonCell];
NSActionCell *cancelCell = [[self.searchField cell] cancelButtonCell];
searchCell.target = self;
searchCell.action = @selector(searchBtnClicked:);
cancelCell.target = self;
cancelCell.action = @selector(cancelBtnClicked:)
}