前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >macOS APP从零到上架

macOS APP从零到上架

作者头像
Helloted
发布2022-06-08 10:25:49
7990
发布2022-06-08 10:25:49
举报
文章被收录于专栏:Helloted

有一款软件叫SimPholders,可以访问iOS开发模拟器的沙盒文件位置,最近,模仿这个功能,开发了一个小型的macOS APP可以一键访问沙盒位置,已经上架到APP Store,记录一下开发过程和上架过程。

一键直达沙盒:iSandBox-APP Store

0、初始化

xcode新建工程,并且run起来,会发现和iOS项目结构类似

AppDelegate:里面有App启动和终止的代理方法:

代码语言:javascript
复制
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}

ViewController:继承自NSViewController,是项目启动后的第一个窗口视图。

1、NSViewController/NSWindowContorller

在iOS上,APP只有一个Window,所有的View都在这个唯一的Window上,所以我们不需要管理Window,但是,在macOS上可以有多个窗口Window,所以相对应的有NSWindow和NSWindowContorller这样的类来管理Window。这里的Window指的是左上角有扩大缩小关闭按钮的窗口。

通过Main.storybord的箭头导向,指向的是主Window,然后将第一个页面指向为ViewController。我们一般在ViewController内管理我们自己的View

可以通过代码的方式自定义WindowController和ViewController

代码语言:javascript
复制
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
     FirstWindowController *firstWindowC = [[FirstWindowController alloc]init];
     NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable ;
     NSWindow *window0 = [[NSWindow alloc]initWithContentRect:CGRectMake(0, 0, 500, 200) styleMask:style backing:NSBackingStoreBuffered defer:YES];
    firstWindowC = [[FirstWindowController alloc]initWithWindow:window0];
    [firstWindowC.window center];
    [firstWindowC.window orderFront:nil];
    firstWindowC.window.backgroundColor = [NSColor redColor];
    
    MainViewController *vc = [[MainViewController alloc]init];
    NSView *view = [[NSView alloc]initWithFrame:CGRectMake(0, 0, 200, 100)];
    view.wantsLayer = YES;
    view.layer.backgroundColor = [NSColor yellowColor].CGColor;
    vc.view = view;
    firstWindowC.window.contentViewController = vc;
}

有一点需要注意的是,如果MainViewController的初始化不是通过☑️Xib来初始化,会报错:

代码语言:javascript
复制
 -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: MainViewController in bundle (null).

尝试在控制台打印这个 VC 的 view,也无法得到相关信息。原因在于macOS 中创建 NSViewController 不会自动创建 view.View默认也不会创建layer,所以需要自定义View.

我这个App需要的窗口只有一个,所以不再详细阐述NSViewController/NSWindowContorller的用法

2、Dock菜单

在info.plist里加LSUIElement为YES可以让App启动后,图标不出现在Dock栏。

右击Dock栏会有默认菜单列表

如果要自定义右键的菜单列表,则在appdelegate里面添加方法

代码语言:javascript
复制
-(NSMenu *)applicationDockMenu:(NSApplication *)sender{
    NSMenu * menu = [[NSMenu alloc]initWithTitle:@"Menu"];
    
    // title是名称,action是点击后操作,keyEquivalent是快捷键
    NSMenuItem * item1 = [[NSMenuItem alloc]initWithTitle:@"菜单1" action:@selector(click) keyEquivalent:@""];
    item1.target = self;
    NSMenuItem * item2 = [[NSMenuItem alloc]initWithTitle:@"菜单2" action:@selector(click) keyEquivalent:@""];
    item2.target = self;
    NSMenuItem * item3 = [[NSMenuItem alloc]initWithTitle:@"菜单3" action:@selector(click) keyEquivalent:@""];
    NSMenu * subMenu = [[NSMenu alloc]initWithTitle:@"subMenu"];
    NSMenuItem * item4 = [[NSMenuItem alloc]initWithTitle:@"菜单4" action:@selector(click) keyEquivalent:@""];
    item4.target = self;
    [subMenu addItem:item4];
    [menu addItem:item1];
    [menu addItem:item2];
    [menu addItem:item3];
    [menu setSubmenu:subMenu forItem:item3];
    return menu;
}

- (void)click{
    NSLog(@"did click");
}

效果如下

3、状态栏

状态栏的菜单是我这个APP最重要的UI,因为沙盒APP都要显示在这里。

代码语言:javascript
复制
@property (nonatomic, strong) NSStatusItem *statusItem;  // 状态栏配置
@property (nonatomic, strong) NSMenu *mainMenu; // 状态栏图标点击后的菜单显示

状态栏图标的配置

代码语言:javascript
复制
- (void)customStatusItem{
    _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
    
    // status栏的图片,16*16pt
    _statusItem.button.image = [NSImage imageNamed:@"status_bar"];
    
    // 点击后的status栏的图片,一般用白色的
    _statusItem.button.alternateImage = [NSImage imageNamed:@"status_bar_white"];
    
    _statusItem.menu = self.mainMenu;
}

菜单栏配置

代码语言:javascript
复制
    NSMenuItem *aboutItem  = [[NSMenuItem alloc] initWithTitle:@"关于iSandBox" action:@selector(appAbout) keyEquivalent:@""];
    aboutItem.tag = about_Tag;
    aboutItem.target = self;
    [self.mainMenu addItem:aboutItem];
  
    [self.mainMenu addItem:[NSMenuItem separatorItem]];
    
    [self.mainMenu addItemWithTitle:@"退出" action:@selector(terminate:) keyEquivalent:@"q"];

效果如下

4、获取模拟器

在mac的终端执行

代码语言:javascript
复制
xcrun simctl list -j devices

能够获取到如下的信息

代码语言:javascript
复制
{
  "devices" : {
    "com.apple.CoreSimulator.SimRuntime.iOS-13-1" : [
      {
        "state" : "Shutdown",
        "isAvailable" : true,
        "name" : "iPhone 8",
        "udid" : "12BD0613-9BFF-4305-B20B-898A8221A9FB"
      },
      {
        "state" : "Shutdown",
        "isAvailable" : true,
        "name" : "iPhone 8 Plus",
        "udid" : "4F454B1A-5CE6-4CAD-A47F-6CFE7DFDBA1D"
      },
      {
        "state" : "Shutdown",
        "isAvailable" : true,
        "name" : "iPhone 11",
        "udid" : "6A579513-24EF-4983-BB68-644F4195551D"
      },
      {
        "state" : "Booted",
        "isAvailable" : true,
        "name" : "iPhone 11 Pro",
        "udid" : "433B9894-56CC-430E-A9FB-C16A773551C9"
      },
      {
        "state" : "Shutdown",
        "isAvailable" : true,
        ...

能够获取到模拟器的状态和Udid。

在代码中,我们不能使用这样的命令来获取,因为xcrun实际上相当于是快捷方式,必现找到xcode路径,找到simctl的实际path

代码语言:javascript
复制
        NSTask *task = [NSTask new];
        NSString *path = [NSString stringWithFormat:@"%@/Contents/Developer/usr/bin/simctl",xcodeURL.path];
        
        [task setLaunchPath:path];
        [task setArguments: @[@"list", @"-j", @"devices"]];
        
        NSPipe *output = [NSPipe new];
        task.standardOutput = output;

        [task launch];
        [task waitUntilExit];

        NSData *data = output.fileHandleForReading.readDataToEndOfFile;
        NSDictionary *resultJson = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];

resultJson就是模拟器列表的字典数据。

5、获取应用

udid有什么用呢,通过udid我们就能获取到应用列表,应用列表在下面这个路径

代码语言:javascript
复制
file:///Users/haozhicao/Library/Developer/CoreSimulator/Devices/4F454B1A-5CE6-4CAD-A47F-6CFE7DFDBA1D/data/Containers/Bundle/Application/

其中,4F454B1A-5CE6-4CAD-A47F-6CFE7DFDBA1D就是udid,通过拼接路径,可以获取到应用列表

有了应用的path,我们就能获取到应用的info.pliset,从而获取相关信息

代码语言:javascript
复制
        NSURL *appInfoPath = [_url URLByAppendingPathComponent:@"Info.plist"];
        NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfURL:appInfoPath];
        NSString *bundleId = infoDict[@"CFBundleIdentifier"];
        NSString *bundleDisplayName = infoDict[@"CFBundleDisplayName"] ?: infoDict[@"CFBundleName"] ;
        NSString *bundleShortVersion = infoDict[@"CFBundleShortVersionString"];
        NSString *bundleVersion = infoDict[@"CFBundleVersion"];
        NSString *icon = ((NSArray *)infoDict[@"CFBundleIcons"][@"CFBundlePrimaryIcon"][@"CFBundleIconFiles"]).firstObject;

图标、应用名称、版本号都可以获取到。

将获取到的信息,自定义一个NSMenuItem插入到mainMenu里,如下显示

点击跳转到沙盒目录

代码语言:javascript
复制
- (void)openAppDocument:(ApplicationMenuItem *)menu
{
    HTAppInfo *appInfo = menu.app;
    NSURL *appUrl = [self getAppDocumentUrl:appInfo];
    if (appUrl) {
        [[NSWorkspace sharedWorkspace] openURL:appUrl];
    }
}
6、上架篇

向App Store的提审过程,被拒了两次,第一次是因为上架的APP必须是沙盒App,所以在项目内要添加沙盒相关配置

另外一个原因,是因为macOS从mojava版本后,有了深色模式,所以状态栏必须要有深色模式的图标

将以上问题处理完毕后顺利上架,整个提审上架过程与iOS差不多。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、初始化
  • 1、NSViewController/NSWindowContorller
  • 2、Dock菜单
  • 3、状态栏
  • 4、获取模拟器
  • 5、获取应用
  • 6、上架篇
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档