Swift App Auto-Login not working - swift

I followed this tutorial here to create an auto-login feature for ma Swift app: https://theswiftdev.com/2015/09/17/first-os-x-tutorial-how-to-launch-an-os-x-app-at-login/
The tutorial is pretty straightforward - the only problem is that I simply can not make it work with my app - it just doesn't work.
I have created a new project for the auto-login with the identifier "com.sascha-simon.com.NetWorkerAutoStarter".
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let mainIdentifier = "com.sascha-simon.Mac.NetWorker"
let running = NSWorkspace.shared().runningApplications
var alreadyRunning = false
for app in running
{
if app.bundleIdentifier == mainIdentifier
{
alreadyRunning = true
break
}
}
if !alreadyRunning
{
DistributedNotificationCenter.default().addObserver(self, selector: #selector(AppDelegate.terminate), name: NSNotification.Name("killme"), object: mainIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast()
components.removeLast()
components.removeLast()
components.append("MacOS")
components.append("NetWorker")
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared().launchApplication(newPath)
}
else
{
self.terminate()
}
}
func applicationWillTerminate(_ aNotification: Notification)
{
// Insert code here to tear down your application
}
func terminate()
{
NSApp.terminate(nil)
}
My Main-App has the identifier "com.sascha-simon.NetWorker"
AppDelegate:
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let starterIdentifier = "com.sascha-simon.NetWorkerAutoStarter"
var startedAtLogin = false
for app in NSWorkspace.shared().runningApplications
{
if app.bundleIdentifier == starterIdentifier
{
startedAtLogin = true
break
}
}
if startedAtLogin
{
DistributedNotificationCenter.default().post(name: NSNotification.Name("killme"), object: Bundle.main.bundleIdentifier!)
}
}
I have a NSPopup with a checkbox to enable the auto login:
#IBAction func startOSCheckedChange(_ sender: NSButton)
{
let value = sender.state == 1
SMLoginItemSetEnabled("com.sascha-simon.NetWorkerAutoStarter" as CFString, value)
}
The method returns true. I have exported the developer-id signed app, started it, enabled the auto login, logged out...and the app didn't start.
I have set an alert to the auto start project and the problem is, that it seems that the project is not started (and therefore the main project isn't started).
First I thought that I have to add a storyboard/MainMenu.xib file to the project (and add the key to the info.plist) but that didn't help either.
What could I have forgotten?

Related

Support URL schemes in macOS application

There are several (old) questions on this subject, but none of the solutions worked for me, so here's the question:
How to add a URL scheme, so it will be possible to open my app via the browser?
I did the following:
Added the required info to the info.plist:
I Added those functions:
func applicationWillFinishLaunching(_ notification: Notification) {
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(handleEvent(_:with:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
#objc func handleEvent(_ event: NSAppleEventDescriptor, with replyEvent: NSAppleEventDescriptor) {
NSLog("at handleEvent")
}
I also tried to add this function:
func application(_ application: NSApplication, open urls: [URL]) {
for url in urls {
NSLog("url:\(url)")
}
}
None of the above worked. I have a webpage that redirect with MyAppLogin://test but nothing happens. It doesn't matter if the app is open or closed (I want it to work in both cases)
Any idea what's the problem here?
Edit: Two more details:
The app is sandboxed
I'm running it via Xcode (so the installation is not at the 'Applications' folder)
One year later, this is how I made it work:
Add this to your AppDelegate:
func applicationWillFinishLaunching(_ notification: Notification) {
let appleEventManager: NSAppleEventManager = NSAppleEventManager.shared()
appleEventManager.setEventHandler(self, andSelector: #selector(handleGetURLEvent(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
Add this, to parse the URL and do whatever you want:
#objc func handleGetURLEvent(_ event: NSAppleEventDescriptor, withReplyEvent: NSAppleEventDescriptor) {
if let urlString = event.forKeyword(AEKeyword(keyDirectObject))?.stringValue {
let url = URL(string: urlString)
guard url != nil, let scheme = url!.scheme else {
//some error
return
}
if scheme.caseInsensitiveCompare("yourSchemeUrl") == .orderedSame {
//your code
}
}
}

How enable autostart for a macOS menu bar app?

I am building an macOS app for the menu bar and it should automatically start with system start.
I started with implementing the autostart functionality for a standard window based macOS app following this tutorial this tutorial. I have
added a new target inside the main project (the helper app)
changed skip install to yes for the helper app
set the helper app to be a background only app
added a new copy file build phase to the main application to copy the helper application into the bundle
linked the ServiceManagement.framework
Implemented the functionality in the app delegates, that the helper app gets launched with system start. After it has launched, it launches the main app (see the tutorial link for more info or the source code down below)
That worked fine, the app launched automatically :) So I started changing the project, that the main application becomes a menu bar app. However than, the app wouldn't auto launch anymore :/ Does someone have a solution for that?
Heres the code of the app delegate of the main app:
import Cocoa
import ServiceManagement
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.button?.title = "Test"
statusItem.button?.target = self
statusItem.button?.action = #selector(showWindow)
// auto start
let launcherAppId = "com.####.####Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == launcherAppId }.isEmpty
SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func showWindow() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unable to find main view controller")
}
guard let button = statusItem.button else {
fatalError("Unable to find status item button")
}
let popover = NSPopover()
popover.contentViewController = vc
popover.behavior = .transient
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
And this is the app delegate of the helper app:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.####.####"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
if !isRunning {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast()
components.removeLast()
components.removeLast()
components.append("MacOS")
components.append("####") //main app name
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func terminate() {
NSApp.terminate(nil)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
Thank you very much for your help :)
My code looks pretty much the same, except how I compose the path in the helper app:
var pathComponents = (Bundle.main.bundlePath as NSString).pathComponents
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
let newPath = NSString.path(withComponents: pathComponents)
NSWorkspace.shared.launchApplication(newPath)
Also, if I remember correctly, I had to make sure the Main.storyboard file still had the "Application Scene" with an Application object and an empty main menu.

How can my OS X app accept drag-and-drop of picture files from Desk in Cocoa?

I got an error when I drag files to my macOS app,
[sandbox] Failed to get a sandbox extension,when i set App Sandboxvalue boolean no,it is ok,but i want put my app to appstore,I must set App Sandbox YES, how can I do?
class FYOpenDragFileView: NSView{
override func draggingEnded(_ sender: NSDraggingInfo) {
print("松手了")
setupWithActive(active: false)
}
override func draggingExited(_ sender: NSDraggingInfo?) {
isDraging = false
setupWithActive(active: false)
print("draggingExited 进去又出来")
}
override func updateDraggingItemsForDrag(_ sender: NSDraggingInfo?) {
guard let next = delegate else {
return;
}
next.fileDraging()
print("更新拖动文件")
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
guard let items = sender.draggingPasteboard.pasteboardItems else{
return false
}
var datas = [Data]()
for i in 0..<items.count{
let item = items[i] .string(forType: .fileURL)
if item != nil {
// this have an error
//[sandbox] Failed to get a sandbox extension
let da = try? Data(contentsOf: URL(string: item!)!)
guard let next = da else {
continue
}
datas.append(next)
}
}
QiniuUploadManger.uploadImage(nil, data: datas)
return true
}
}
With sandboxing, you'll have to set the appropriate "entitlements" for your app to receive drag-&-dropped files, which is probably why you're getting this error.
The entitlements file should already have been generated for you when you enabled Sandboxing. See this Apple documentation for details.

Can't get QuickLook to work when trying to preview files

I am writing a macOS application with Swift using story boards. I have a NSTableView which contains files that I want the user to be able to preview via QuickLook.
I seemingly have everything in place and my code looks very similar to what has been described here: QuickLook consumer as a delegate from an NSViewController, but I keep getting the error
-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
I've been trying to adapt the solution of above post to my situation with Swift and story boards.
The main pieces are:
import Quartz
class ViewController: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let windowNextResponder = self.view.window?.nextResponder
self.view.window?.nextResponder = self
self.nextResponder = windowNextResponder
}
// *** Quicklook stuff ***
#IBAction func quickLookButtonAction(_ sender: Any) {
guard qlPanel != nil else {
return
}
if qlPanel!.currentController == nil {
print ("No controller")
//qlPanel!.windowController = self.view.window?.windowController
// qlPanel!.updateController()
} else {
print (qlPanel!.currentController)
}
qlPanel!.delegate = self
qlPanel!.dataSource = self
qlPanel!.makeKeyAndOrderFront(self)
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return CSVarrayController.selectedObjects.count
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
let file = CSVarrayController.selectedObjects[index] as! CSVfile
return file.url as NSURL
}
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = self
panel.delegate = self
}
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = nil
panel.delegate = nil
}
}
With or without messing with the responder chain I get the error.
The delegate functions all get called as expected as well.
Remove
qlPanel!.delegate = self
qlPanel!.dataSource = self
in quickLookButtonAction, the viewcontroller isn't in control yet. Wait for beginPreviewPanelControl.
From the documentation for currentController:
You should never change the preview panel’s state (its delegate, datasource, and so on) if you are not controlling it.
From comments in QLPreviewPanel.h for -beginPreviewPanelControl::
Sent to the object taking control of the Preview Panel.
The receiver should setup the preview panel (data source, delegate, binding, etc.) here.

Basic Sinch Sample in Swift - but no Sound

first of all thank you for reading my lines.
For an idea I'm currently trying to dive into the Swift world (I only have very basic programming knowledge - no Objective C knowledge
).
I tried to set up the following lines to create a very basic app-to-app sample in Sinch. After my code I let you know what the issues are.
import UIKit
import Sinch
var appKey = "APP_KEY_FROM_MY_ACCOUNT"
var hostname = "clientapi.sinch.com"
var secret = "SECRET_FROM_MY_ACCOUNT"
class CViewController: UIViewController, SINCallClientDelegate, SINCallDelegate, SINClientDelegate {
var client: SINClient?
var call: SINCall?
var audio: SINAudioController?
//Text field in the main storyboard
#IBOutlet weak var userNameSepp: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.initSinchClient()
}
//initialize and start the client as a fixed "userA"
func initSinchClient() {
client = Sinch.client(withApplicationKey: appKey, applicationSecret: secret, environmentHost: hostname, userId: "userB")
client?.call().delegate = self
client?.delegate = self
client?.startListeningOnActiveConnection()
client?.setSupportCalling(true)
client?.start()
}
//Did the Client start?
func clientDidStart(_ client: SINClient!) {
print("Hello")
}
//Did the Client fail?
func clientDidFail(_ client: SINClient!, error: Error!) {
print("Good Bye")
}
//Call Button in the main.storyboard ... if call==nil do the call ... else hangup and set call to nil
//the background color changes are my "debugging" :D
#IBAction func callSepp(_ sender: Any) {
if call == nil{
call = client?.call()?.callUser(withId: userNameSepp.text)
//for testing I change to callPhoneNumber("+46000000000").
// the phone call progresses (but I hear nothing),
// the phonecall gets established (but I hear nothing)
// and the phonecall gets ended (but of course I hear nothing)
self.view.backgroundColor = UIColor.red
call?.delegate = self
audio = client?.audioController()
}
else{
call?.hangup()
self.view.backgroundColor = UIColor.blue
call = nil
}
}
func callDidProgress(_ call: SINCall?) {
self.view.backgroundColor = UIColor.green
client?.audioController().startPlayingSoundFile("/LONG_PATH/ringback.wav", loop: true)
print("Call in Progress")
}
//I know that this works but I don't hear anything
func callDidEstablish(_ call: SINCall!) {
client?.audioController().stopPlayingSoundFile()
print("Call did Establish")
}
func callDidEnd(_ call: SINCall!) {
print("Call did end")
}
// this works fine
#IBAction func hangUpSepp(_ sender: Any) {
call?.hangup()
self.view.backgroundColor = UIColor.red
call = nil
}
// i work in a "sub view controller" - so i navigate here back to the main view controller
#IBAction func goBackMain(_ sender: Any) {
call?.hangup()
dismiss(animated: true, completion: nil)
client?.stopListeningOnActiveConnection()
client?.terminateGracefully()
client = nil
}
}
So I can call my private phone number or if I change to callUser I can call another app but I don't hear anything. What do I miss? It must have to do with the SINAudioController and the client's method audioController() but I don't know what I'm doing wrong. Thank you for your help.