Swift - CLLocationManager not asking for user permission - cocoa

I am with Xcode beta 2 and trying to get user location with OS X app, however it is not even asks user permission. here is what I did until now.
...
import CoreLocation
class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDelegate {
var locManager : CLLocationManager = CLLocationManager()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
locManager.delegate = self
locManager.desiredAccuracy = kCLLocationAccuracyBest
locManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
//doSomething.
}
}
nothing happens. I looked other questions and try all suggested solutions from answers but did not work. Tried with Objective-C, all fine. Am I missing something here?
Location Service enabled on preferences of the computer. Also "NSLocationUsageDescription" is in info.plist. NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription for 10.10 and later, I am working on 10.9.
Information Property List Key Reference

There were no problem code wise, just noticed it is App Sandbox entitlement problem. Once I have checked Location under App Data it worked right away.

Related

Custom redirect URI to macOS app not working

Purpose of the redirect
I am working on an application that integrates with Cisco's Webex Teams Api. Unfortunately, for macOS they don't have an SDK (they only do have one for iOS), so I am trying to authenticate with their Api kind of "manually".
So, I am using a custom URL that has a client id backed in to retrieve a code. When calling this URL, Cisco's regular login procedure starts, asking the user to log in with her / his username an password. Upon successful login, Cisco's server provide you with a URL that includes a code that is then needed to proceed.
What I got so far
Custom url
My prototype currently just consists of a button that calls a custom URL to authenticate. To get this URL, I needed to register my integration with Cisco here: https://developer.webex.com/my-apps
So far, when I click on my button, a WKWebView instance takes over with my custom URL:
https://api.ciscospark.com/v1/authorize?client_id=<**personalClientId**>&response_type=code&redirect_uri=<**BundleIdentifier**>3A%2F%2Fredirect&scope=spark%3Aall%20spark%3Akms&state=set_state_here
Redirect URI
So, my redirect uri currently is ch.appfros.webexoauthwokflowprototype://redirect; this redirect uri is registered with Cisco in my integration registration form.
I am aware that I have to put this redirect uri into my app, so I did that resulting in the corresponding info.plist section looking like this:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>response</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ch.appfros.webexoauthwokflowprototype://</string>
</array>
</dict>
</array>
Preparing the AppDelegate
As I far as I currently have found out, it seems that I need to also have an additional function in my AppDelegate, that handles the callback. For cocoa, this seems to be the func application(_ application: NSApplication, open urls: [URL]) method.
As far as I understand it, i would have to handle all redirects there. My AppDelegate currently looks like this:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func application(_ application: NSApplication, open urls: [URL]) {
print("got answer")
}
}
The issue I am having
My problem is, fulfilling the login procedure results in the message There is no application set to open the URL ch.appfros.webexoauthwokflowprototype://redirect?code=&state=loggedIn.
So, the authentication process on Cisco's end is successful and it provides my system with the URL -- which my computer does not know how to handle... I can even see the code I need to retreive an access in the URL that is coming back, I just don't have them within my application to proceed...
What am I missing?
It's quite obvious that I am missing something crucial here -- I just don't know what it is and researching online wouldn't help me. I could find bits and pieces here and there, but I am still to find a concise instruction on what to do in my case for my macOS app.
Environment
Xcode 10.1
Swift 4.2
Cocoa app
Cisco Webex Teams integration with authentication URL
Edit: changes to AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application)
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func application(_ application: NSApplication, open urls: [URL]) {
print("got answer")
}
#objc
func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
print("got answer in event handler")
}
}
Edit 2: update to AppDelegate (to reflect hints and tips by NoHalfBits)
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application)
}
func applicationWillFinishLaunching(_ notification: Notification) {
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func application(_ application: NSApplication, open urls: [URL]) {
print("got answer")
}
#objc
func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
print("got answer in event handler")
}
}
In the Info.plist, specify the URL scheme in the array under the CFBundleURLSchemes key without the :// suffix (ch.appfros.webexoauthwokflowprototype instead of ch.appfros.webexoauthwokflowprototype://).
Btw, Apple recommends using a reverse-DNS style identifier for CFBundleURLName (the name, not the scheme itself).
I've set up a minimal test project: Xcode 10.1, Cocoa app Swift template. The Info.plist part defining the custom scheme is:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>no.half.bits</string>
<key>CFBundleURLSchemes</key>
<array>
<string>nohalfbits</string>
</array>
</dict>
</array>
The app delegate just is:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func application(_ application:NSApplication, open urls: [URL]) {
print("openURLs:", urls)
}
}
This works as expected when testing with a small command line tool:
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[])
{
#autoreleasepool
{
NSURL* url = [NSURL URLWithString:#"nohalfbits://hello.world"];
// is there app that handles the scheme, and where is it
NSLog(#"appURL = %#", [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:url]);
// actually open the url; the app should log this
[NSWorkspace.sharedWorkspace openURL:url];
}
return 0;
}
I also added a WKWebView to the project and let it load a minimal webpage from a server, just containing a link with the custom scheme - as expected, clicking this link triggers the openURL method with the custom scheme (had to add the required NSAppTransportSecurity dictionary to the Info.plist and enable outgoing connections in the sandbox settings of the project).
In the past, I've seen some 3rd party browsers chocking on custom schemes containing hyphens and dots. For WKWebView and the underlying custom URL scheme handling in the os this seems to be no problem; no.half-bits instead of nohalfbits works as expected when testing with the NSWorkspace methods and the WKWebView.
Turns out that sandboxing really was the problem. After disabling Sandboxing -- this is going to be a small in house helper at my comanpy, nothing fancy and for sure nothing for any store -- it worked right away.
If need be I will have to take a look into how NSAppTransportSecurity would be needed to get implemented -- thanks to #NoHalfBits for pushing me into the right direction.
So now I have
the custom url registered
App Sandboxing disabled
func application(_ application: NSApplication, open urls: [URL]) implemented in my AppDelegate (turns out this really is enough as #Aaron Raimist was saying
Thanks for your help; hope this might help anybody else (or my future self) down the road :-)

WKCrownDelegate doesn't seem to be working with Xcode 9 GM

I've tried the code below targeting both iOS 10.0/Watch OS 3.0 and iOS 11.0/Watch OS 4.0, and tested both in the simulator and my Watch OS 4 device. Nothing seems to trigger the crownDidRotate delegate method.
Simple interface with one label connected to the outlet. I know it's connected because I change the text in the awake method. Breaking in the delegate method never stops when I rotate the crown.
Any ideas?
import Foundation
import WatchKit
class InterfaceController: WKInterfaceController, WKCrownDelegate {
var value = 1
#IBOutlet var label: WKInterfaceLabel!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
label.setText("Yeah?")
crownSequencer.delegate = self
crownSequencer.focus()
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
label.setText("Rotational: \(rotationalDelta)")
}
}
I had the same experience. As a hack, I added another call to crownSequencer.focus() in willActivate(), and I'm now seeing events. (xcode 9.0 gm, ios 11.0 gm, watchos 4.0 gm)
Adding crownSequencer.focus() in willActivate() did not help me in Xcode10.
You don't have to call crownSequencer.focus() neither in awake() or in willActivate() but in didAppear(). So you need to add the following lines:
override func didAppear() {
super.didAppear()
crownSequencer.focus()
}

Swift2.3 code for Beacon detection

We are in advanced stages of developing a Swift2.2 app and hence have decided to migrate to 2.3 in the interim and do the full Swift 3 migration later. However we are unable to get beacon detection working post conversion to Swift 2.3. The method "didRangeBeacons" keeps returning an empty array. The same code was working in Swift 2.2 so we know we have all the permissions etc in place.
Also if we open the "Locate" app on the same ipad then our app also starts returning data in "didRangeBeacons". Have tried various versions of the apps out there and all Swift2.3 apps are behaving the same way. Can't make out what the Locate app is doing... Anyone on the same boat??
Here is the code we are using. Am not sure this is supposed to be written here or in comments but couldn't put code within comments somehow...
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
let region = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "9735BF2A-0BD1-4877-9A4E-103127349E1D")!, identifier: "testing")
// Note: make sure you replace the keys here with your own beacons' Minor Values
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startMonitoringForRegion(self.region)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) {
print("didStartMonitoringForRegion")
self.locationManager.requestStateForRegion(region)
}
func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
print("monitoringDidFailForRegion")
}
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
print("didDetermineState")
if state == .Inside {
//Start Ranging
self.locationManager.startRangingBeaconsInRegion(self.region)
self.locationManager.startUpdatingLocation()
}
else {
//Stop Ranging here
self.locationManager.stopUpdatingLocation()
self.locationManager.stopRangingBeaconsInRegion(self.region)
}
}
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
print(beacons.count)
}
}
[Update post some more attempts to get this working]
App works in foreground mode if we remove self.locationManager.startMonitoringForRegion(self.region)
and call self.locationManager.startRangingBeaconsInRegion(self.region)
directly after self.locationManager.requestAlwaysAuthorization()
This is sub-optimal because we don't get entry and exit events or state but atleast we are getting beacon counts.
There are a number of anecdotal reports of beacon detection problems on iOS 10. Symptoms include:
Improper region exit events, especially when the app is in the background, followed by entry events if the shoulder button is pressed.
Periodic dropouts in detections of ranged beacons, with callbacks providing an empty beacon list when beacons are in the vicinity.
Ranged beacon callbacks return proper results when a different beacon ranging or detection app is running that targets iOS 9.x.
This is presumably a bug that will be fixed in an iOS update. Until then, some users have reported that setting the app deployment target in XCode to 9.x will resolve the issue.
Try constructing your location manager after the view loads. In other words, change:
let locationManager = CLLocationManager()
to
let locationManager : CLLocationManager!
And then add this to the viewDidLoad:
locationManager = CLLocationManager()
I have seen strange behavior with LocationManager constructed on initialization of a UIViewController.
While I was testing beacon ranging, I downloaded multiple applications that I forgot to uninstall. After I uninstalled all beacon-related applications and re-installed just the needed applications, I was able to get ranging beacons to work.

Why is my app crashing when loading my in app purchases?

I have this code that loads my in app purchases in the didMoveToView. If I press a button to go to my help screen or another button to leave the scene before the in app purchases load the app crashes. I think Im calling it in the wrong place. Can someone tell me what Im doing wrong? If you need more code or info let me know please. Thanks!
override func didMoveToView(view: SKView) {
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "unlockLevelTwo", "unlockLevelThree", "unlockEverything")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
#1
Did you add the StoreKit framework to your project?
#2
Make sure not to release the class containing the corresponding methods at the end.
Hope that helps :)

How to retrieve the OSX application that currently receives key events

I am following the cocoa documentation to determine the current front most application in OSX - aka the application which receives key events.
However, when I am executing the following swift the API always returns me the same value - XCode, but it never changes to chrome or any other application when I switch to them. I also tried to execute the compiled program but instead of constantly showing XCode it now shows whichever terminal app I am running.
What is the correct way of determining the application that receives the key events from OSX? Is my code in this regard broken?
import Cocoa
func getActiveApplication() -> String{
// Return the localized name of the currently active application
let ws = NSWorkspace.sharedWorkspace()
let frontApp = ws.frontmostApplication
return frontApp.localizedName
}
var frontMostApp : String
while true {
frontMostApp = getActiveApplication();
NSLog("front app: %#", frontMostApp)
sleep(1);
}
This thread is a bit old but was very helpful. I did some research based on Marco's post and uchuugaka's answer. The following is the result.
// swift 3.0
// compile: xcrun -sdk macosx swiftc -framework Cocoa foo.swift -o foo
// run: ./foo
import Cocoa
class MyObserver: NSObject {
override init() {
super.init()
NSWorkspace.shared().notificationCenter.addObserver(self,
selector: #selector(printMe(_:)),
name: NSNotification.Name.NSWorkspaceDidActivateApplication,
object:nil)
}
dynamic private func printMe(_ notification: NSNotification) {
let app = notification.userInfo!["NSWorkspaceApplicationKey"] as! NSRunningApplication
print(app.localizedName!)
}
}
let observer = MyObserver()
RunLoop.main.run()
I am a newbie in both Cocoa and Swift. I don't know if the above is efficient, but it works for me. I got help from How to create a minimal daemon process in a swift 2 command line tool? and Swift 3 NSNotificationCenter Keyboardwillshow/hide among numerous others.
Swift 4:
NSWorkspace.shared.notificationCenter.addObserver(self, // HERE, shared
selector: #selector(printMe(_:)),
name: NSWorkspace.didActivateApplicationNotification, // HERE
object:nil)
Edit (Swift 4)
The compiler says the printMe function must be #objc. (I don't know the meaning, but it worked when I prepend #objc at the beginning of the line. Here is the full code for easy copy-paste.
// swift 4.0
// compile: xcrun -sdk macosx swiftc -framework Cocoa foo.swift -o foo
// run: ./foo
import Cocoa
class MyObserver: NSObject {
override init() {
super.init()
NSWorkspace.shared.notificationCenter.addObserver(self,
selector: #selector(printMe(_:)),
name: NSWorkspace.didActivateApplicationNotification,
object:nil)
}
#objc dynamic private func printMe(_ notification: NSNotification) {
let app = notification.userInfo!["NSWorkspaceApplicationKey"] as! NSRunningApplication
print(app.localizedName!)
}
}
let observer = MyObserver()
RunLoop.main.run()
You you should do one thing differently, that is follow the NSWorkSpace notifications that tell you the applications resigned active or became active.
This is key especially as you are running in debug mode.
In debug mode Xcode is spawning your app as a child process.
In release mode it is basically doing the same as calling the open command in terminal.
In debug mode if you call this code too early and only once, you're not catching changes. Remember it's dynamic.

Resources