MacOS应用开发学习笔记

系统学习MacOS应用开发,但是细小的知识点容易忘记,因此在此做下笔记,说不定后续有时间回顾下!

第1章 准备工作

  • Apple现在要求上架Mac AppStore的应用必须开启沙盒SandBox,该项在项目配置中打开。
  • 如果你的应用要上架Mac AppStore且需要访问服务器的API接口,必须打开SandBox中Network下的Outgoing Connections选项, 同时Hardware下的Printing选项也要开启,否则审核不通过。
  • 如果使用了第三方的Framework或者自己开发的Framework,Code Signing中的Other Code Signing Flags必须设置为--deep,否则无法打包发布到App Store。

第2章 窗口对象

2.1 窗口界面组成

  • 可以接收输入事件(键盘、鼠标、触控板等)的窗口对象称为keyWindow(注意,全称为keyboard Window);当前的活动窗口称为MainWindow,一个时刻只能有一个keyWindow和一个mainWindow,当mainWindow可以接收输入事件时两者是同一个窗口。

2.2 模态窗口

某些场景需要限制用户必须处理完当前窗口的任务,此时需要 用到模态。模态的方式有两种:
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];
}
  1. Modal Sessions:上述相比温柔些,允许响应快捷键和系统菜单
NSModalSession sessionCode = [[NSApplication sharedApplication] beginModalSessionForWindow:window];
 - (void)windowWillClose:(NSNotification *)notify {
    if (sessionCode != 0) {
        [[NSApplication sharedApplication] endModalSession:sessionCode];
    }
}

总结:
1.任何窗口的关闭要么通过左上角关闭按钮,要么通过执行window的close方法关闭。
2.对于任何一种模态窗口,关闭后还要额外调用结束模态方法结束状态,若不执行,则其他窗口无法正常工作。

2.3 窗口编程控制

  • 通过懒加载方式创建并显示窗口,如下:
@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];
}

注意:通过上述代码创建的窗口只能显示和关闭一次,后续会崩溃!后续章节有详细分析。

  • 窗口通知
    在这里插入图片描述
    当多窗口切换,两个窗口之间界面修改会影响另一个的数据模型时,通过监听NSWindowDidBecomeMainNotificationNSWindowDidBecomeKeyNotification重新获取数据刷新界面。

  • 如何给NSWindow的contentView添加元素?

  1. 通过NSView或者NSViewController的View添加
[self.window.contentView addSubview:view];
  1. macOS10.10以后,创建一个NSViewController子类,直接赋值给NSWindow的contentViewController。
    NSViewController *myVc = [[NSViewController alloc] init];
    self.window.contentViewController = myVc;
  • 设置NSWindow的title和icon
 - (void)setWindowTitleAndIcon {
    [self.window setRepresentedURL:[NSURL URLWithString:@"/Users/zhuzhanlong/Desktop/image.jpeg"]];
    [self.window setTitle:@"TestApp"];
    [[self.window standardWindowButton:NSWindowDocumentIconButton] setImage:[NSImage imageNamed:@"help"]];
}
  • 设置NSWindow的background Color
    [self.window setOpaque:NO];
    [self.window setBackgroundColor:[NSColor greenColor]];
  • 关闭窗口时终止程序
    当关闭最后一个window或者关闭程序的唯一window时,程序退出
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
    return YES;
}

另一种方法是监听NSWindowWillCloseNotification,并判断是mainWindow就关闭程序。

 - (void)receiveWindowWillCloseNotification:(NSNotification *)notify {
    NSWindow *window = notify.object;
    if (window == self.mainWindow) {
        [NSApp terminate:self];
    }
}
  • 点击Dock栏恢复应用
    当未设置上一条中的最后一个窗口关闭时程序退出时,即applicationShouldTerminateAfterLastWindowClosed方法返回NO或者未重写时,当窗口关闭时,点击Dock栏仍然可以恢复应用。代码如下:
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
    [self.strongWindow makeKeyAndOrderFront:self];
    return YES;
}
  • Window的title区域增加视图
    通过获取contentView的父视图,自定义title区域
 - (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];
}
  • NSWindow的位置控制及居中
    由于NSWindow的isRestorable属性会记忆上次位置并自动恢复,因此,如果需要自定义位置或者手动设置位置,先在window的xib或者代码中将isRestorable属性设置为NO。
 - (void)windowDidLoad {
    [super windowDidLoad];
    
    [self.window setRestorable:NO];
    //居中
    [self.window center];
    
    //或者指定在屏幕的固定位置
    NSRect frame = NSMakeRect(0, 0, 100, 100);
    [self.window setFrame:frame display:YES];
}

第3章 视图和滚动条

3.1 NSView

  • 坐标系统
    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;
  • 视图绘制
    默认在drawRect:方法中进行绘制。
	//延迟绘制
    [self.view needsDisplay];
    [self.view setNeedsDisplay:YES];

	//立即绘制
	[self.view display];
    [self.view displayRect:self.view.frame];

注意,在drawRect:方法之外绘制时,需要使用:

	[self.view lockFocus];
    
    [self.view unlockFocus];
  • 视图截图
    使用lockFocus锁定后获取到PDF data并转换成NSData写入文件。核心代码如下:
- (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在X轴或Y轴方向上的滚动

禁用某一方向的滚动,需要继承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];
    }
}

第4章 基本控件

4.1 控件家族图谱

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 NSSearchField

绑定事件用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:)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jarlen John

谢谢你给我一杯咖啡的温暖

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

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

打赏作者

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

抵扣说明:

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

余额充值