macOS Swift:如何正确添加应用程序作为登录项

时间:2017-05-17 16:31:02

标签: objective-c swift macos cocoa macos-sierra

我花了大约一天(可能多一点)试图按照它在macOS启动(用户登录)启动的顺序将我的应用程序添加到Login Item。

  1. 第一种方法是最新方法;我在youtube上查看了这个教程: https://www.youtube.com/watch?v=2mmWEHUgEBo&t=660s
  2. 所以按照这个步骤,我做了:

    1. 在我的主项目中添加名为Launcher
    2. 的新项目
    3. 我使用自动签名(作为我的Xcode的版本)是不同的 enter image description here

    4. 在项目设置>功能我将App Sandbox切换为ON。

    5. 在Build阶段我添加了这个: enter image description here

    6. 我的启动器已跳过安装= YES enter image description here

    7. 我的Launcher应用程序中的代码看起来像这样(我以前甚至使用Swift做同样的事情)

        - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
      {
          // Insert code here to initialize your application
      
          NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
          pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)];
          NSString *path = [NSString pathWithComponents:pathComponents];
          [[NSWorkspace sharedWorkspace] launchApplication:path];
          [NSApp terminate:nil];
      } 
      
    8. 最后,我在主应用程序中使用魔术代码启用应用程序作为登录项

    9.   if(!SMLoginItemSetEnabled("click.remotely.Remotely-Click-Server-Launcher"
       as CFString, Bool(checkboxButton.state as NSNumber) ) ) {
                  let alert: NSAlert = NSAlert()
                  alert.messageText = "Remotely.Click Server - Error";
                  alert.informativeText = "Application couldn't be added as 
              Login Item to macOS System Preferences > Users & Groups.";
                  alert.alertStyle = NSAlertStyle.warning;
                  alert.addButton(withTitle:"OK");
                  alert.runModal();
         }
      
      1. 我制作了Archive,然后有不同的导出选项:
      2. enter image description here

        我无法决定选择哪一个,所以我尝试了所有这些。 "保存Mac App Store部署" - 已安装在/ Applications /目录中的安装包,但应用程序从不运行。 " Developer-Id签名," "发展签名" ," macOS App" all将文件放在我导出到Applications目录的目录中,但没有人工作。

        1. 当我点击复选框按钮时,我可以在屏幕上看到一些窗口闪烁一段时间(Launcher程序)。当我退出并登录同一窗口时,会出现闪烁效果,但Launcher没有启动主应用程序。当我再次单击复选框按钮(并关闭登录项)时,这种对用户登录(系统启动)的闪烁效果不会再次发生。因此,似乎将Launcher程序添加为Login Item可以正常工作,但是这个Launcher无法启动Main应用程序。此外,当我转到/Applications/Main.app/Contents/Library/LoginItems/Launcher.app并手动点击它然后Launcher应用程序正确启动主应用程序(所以路径是正确的)。

        2. 那么出了什么问题?

        3. 然后我考虑使用不推荐的方法的实现 的 kLSSharedFileListSessionLoginItems

          我认为它必须工作只需在系统偏好设置中添加一些内容  窗口如下。

          enter image description here

          但它也可能出错!

          1. 我在Swift中选择了实现(我发现的所有示例/教程都在Objective-C中)所以我写了这样的东西:

             class LoginItemsList : NSObject {
            
            let loginItemsList : LSSharedFileList = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue();
            
            
            
            func addLoginItem(_ path: CFURL) -> Bool {
            
                if(getLoginItem(path) != nil) {
                    print("Login Item has already been added to the list."); 
                    return true;
                }
            
                var path : CFURL = CFURLCreateWithString(nil, "file:///Applications/Safari.app" as CFString, nil);
                print("Path adding to Login Item list is: ", path);
            
                // add new Login Item at the end of Login Items list
                if let loginItem = LSSharedFileListInsertItemURL(loginItemsList,
                                                                  getLastLoginItemInList(),
                                                                  nil, nil,
                                                                  path,
                                                                  nil, nil) {
                    print("Added login item is: ", loginItem);
                    return true;
                }
            
                return false;
            }
            
            
            func removeLoginItem(_ path: CFURL) -> Bool {
            
                // remove Login Item from the Login Items list 
                if let oldLoginItem = getLoginItem(path) {
                    print("Old login item is: ", oldLoginItem);
                    if(LSSharedFileListItemRemove(loginItemsList, oldLoginItem) == noErr) {
                        return true;
                    }
                    return false;
                }
                print("Login Item for given path not found in the list."); 
                return true;
            }
            
            
            func getLoginItem(_ path : CFURL) -> LSSharedFileListItem! {
            
                var path : CFURL = CFURLCreateWithString(nil, "file:///Applications/Safari.app" as CFString, nil);
            
            
                // Copy all login items in the list
                let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue();
            
                var foundLoginItem : LSSharedFileListItem?;
                var nextItemUrl : Unmanaged<CFURL>?;
            
                // Iterate through login items to find one for given path
                print("App URL: ", path);
                for var i in (0..<loginItems.count)  // CFArrayGetCount(loginItems)
                {
            
                    var nextLoginItem : LSSharedFileListItem = loginItems.object(at: i) as! LSSharedFileListItem; // CFArrayGetValueAtIndex(loginItems, i).;
            
            
                    if(LSSharedFileListItemResolve(nextLoginItem, 0, &nextItemUrl, nil) == noErr) {
            
            
            
                        print("Next login item URL: ", nextItemUrl!.takeUnretainedValue());
                        // compare searched item URL passed in argument with next item URL
                        if(nextItemUrl!.takeRetainedValue() == path) {
                            foundLoginItem = nextLoginItem;
                        }
                    }
                }
            
                return foundLoginItem;
            }
            
            func getLastLoginItemInList() -> LSSharedFileListItem! {
            
                // Copy all login items in the list
                let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue() as NSArray;
                if(loginItems.count > 0) {
                    let lastLoginItem = loginItems.lastObject as! LSSharedFileListItem;
            
                    print("Last login item is: ", lastLoginItem);
                    return lastLoginItem
                }
            
                return kLSSharedFileListItemBeforeFirst.takeRetainedValue();
            }
            
            func isLoginItemInList(_ path : CFURL) -> Bool {
            
                if(getLoginItem(path) != nil) {
                    return true;
                }
            
                return false;
            }
            
            static func appPath() -> CFURL {
            
                return NSURL.fileURL(withPath: Bundle.main.bundlePath) as CFURL;
            }
            
             }
            
          2. 我已经用它来点击复选框

            打开/关闭登录项目
              let loginItemsList = LoginItemsList();
            
                if( checkboxButton.state == 0) {
                    if(!loginItemsList.removeLoginItem(LoginItemsList.appPath())) {
                        print("Error while removing Login Item from the list.");
                    }
                } else {
                    if(!loginItemsList.addLoginItem(LoginItemsList.appPath())) {
                        print("Error while adding Login Item to the list.");
                    }
                }
            
          3. 我已经在调试模式(Xcode播放按钮)中运行它并尝试将其存档并导出到/ Applications文件夹(如果重要),但这种方法也不起作用。

          4. 控制台打印的消息。错误表示插入登录项的功能返回nil。

          5. enter image description here

            所以在那之后我甚至尝试使用Objective-C实现这个(来自一些stackoverflow示例)(因为Swift中有许多Unmanaged&lt;&gt;) 所以我添加了新的.m和.h文件以及Bridging-Header.h,然后添加了这样的代码:

            - (void)enableLoginItemWithURL:(NSURL *)itemURL
            {
                LSSharedFileListRef loginListRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
            
                if (loginListRef) {
                    // Insert the item at the bottom of Login Items list.
                    LSSharedFileListItemRef loginItemRef = LSSharedFileListInsertItemURL(loginListRef,
                                                                                         kLSSharedFileListItemLast,
                                                                                         NULL,
                                                                                         NULL,
                                                                                         (__bridge CFURLRef) itemURL,
                                                                                         NULL,
                                                                                         NULL);
                    if (loginItemRef) {
                        CFRelease(loginItemRef);
                    }
                    CFRelease(loginListRef);
                }
            }
            

            简单(只是插入)没有任何铃声和口哨声。 它也有同样的问题, LSSharedFileListInsertItemURL 返回nil,而登录项未添加到系统偏好设置&gt;用户&amp;群组&gt;登录项目。

            所以我知道为什么我不能做这个工作?

            更新1

            我试图在另一台计算机iMac(MacOS Sierra和最新的XCode 8.3)上使用第一种方法(主应用程序中的辅助Launcher应用程序)实现应用程序,它似乎正常工作,所以可能我的操作系统有问题或者Xcode(供应配置文件,应用程序的签名或其他)在MacBook Air上,这种方法不起作用我正在使用OS X El Capitan 10.11.5和Xcode 8.0。

            观看它的工作原理: https://youtu.be/6fnLzkh5Rbs 和测试 https://www.youtube.com/watch?v=sUE7Estju0U

            第二种方法在我的iMac上也不起作用,在执行LSSharedFileListInsertItemURL时返回 nil 。所以我不知道为什么会这样。

            观看它的工作原理: https://youtu.be/S_7ctQLkIuA

            更新2

            从El Capitan 10.11.5升级到macOS Sierra 10.12.5并使用Xcode 8.3.2而不是Xcode 8.0.0后,第二种方法也正常工作,并且正在向系统偏好设置添加登录项&gt;用户&amp;群组&gt;登录项目 重要!要使用LSSharedFileListInsertItemURL这种方法,需要禁用应用沙盒!如下面的视频: https://youtu.be/UvDkby0t_WI

4 个答案:

答案 0 :(得分:4)

几年前,我也为此而苦苦挣扎,最终为其制作了package,这使得为沙盒应用程序添加“登录时启动”功能更加容易。

您只需:

来代替lots of manual steps
import LaunchAtLogin

LaunchAtLogin.isEnabled = true

答案 1 :(得分:1)

上述登录项目编程问题的解决方案既可以使用现代方法与ServiceManagement.framework正确使用,也可以使用旧的(已弃用的)方法将登录项插入到系统偏好设置&gt;用户&amp;群组&gt;登录项目。请参阅我的更新1和更新2备注。

答案 2 :(得分:1)

对于ServiceManagement方法,有时它在您的开发机器中不起作用,因为Xcode的DerivedData中有另一个应用程序副本。系统不知道要启动哪个应用程序。所以转到〜/ Library / Developer / Xcode / DerivedData并删除你的开发副本可能有所帮助。

答案 3 :(得分:0)

亲爱的Michal我对登录项目遇到了同样的问题。登录项目可以通过两种方式添加;来自LSSharedFileListItemRef的一个将显示在首选项的登录项中,但此方法仅适用于非沙盒应用程序,如果您正在制作沙盒应用程序,那么您应该采用另一种方式使用ServiceManagement框架。

您可以查看以下指向所有内容的链接 - : Launching your app on system start

我正在添加另一个在登录时添加应用程序的参考 - : Approach for sandbox app with helper application

您可能无法在登录项目中实施应用程序,但请按照相应步骤进行操作,否则您将获得成功。