Swift2.3 code for Beacon detection - ibeacon

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.

Related

How do I implement drag-and-drop from Photos to my Cocoa app with full quality?

In my drag destination view (NSView subclass), I have implemented:
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
// check for Photos promises
var gotPromised = false
draggingInfo.enumerateDraggingItems(options: [], for: self, classes: [NSFilePromiseReceiver.self], searchOptions: [:], using: {(draggingItem, idx, stop) in
let filePromiseReceiver = draggingItem.item
print("got a file promise receiver: \(filePromiseReceiver)")
gotPromised = true
// Use filePromiseReceiver here for your task.
})
return gotPromised
}
When I run this and drag something over from Photos.app, I get this warning:
How do I fix my drag destination to not have this warning? I would like to get full-quality photos into my app.
Well. Figured it out myself after some investigation. You need to do multiple things (register for correct types, enumerate the items correctly etc), and the warning is not there if you just implement the promise receiving completely and correctly. Apple has provided an example project which implements everything correctly.

What is the correct way to use backgroundCompletionHandler in Alamofire?

I'm not clear on how to use this properly but had seen other people doing this type of thing:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
manager.sharedInstance.backgroundCompletionHandler = completionHandler
}
In our similar implementation, at this point completionHandler is partial apply forwarder for reabstraction thunk helper...
Where manager is (despite being a singleton) essentially:
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.ourcompany.app")
let manager = Alamofire.Manager(configuration: configuration)
However this causes the following warning to be printed in the console:
Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: but the completion handler was never called.
I set a breakpoint here and at this point the message is already visible in the console and backgroundCompletionHandler is nil.
We're building against the iOS 9 SDK with Xcode 7.0 and currently using Alamofire 2.0.2
I originally thought this was introduced when we merged our Swift 2.0 branch but I'm also seeing the message with an earlier commit using Xcode 6.4 against the iOS 8 SDK.
Update 1
To address #cnoon's suggestions:
The identifier matches - the configuration and manager are set inside a singleton so there's no way for it to be wrong.
When adding and printing inside of didSet on backgroundCompletionHandler in the Manager class, the message is logged before the warning.
When printing inside of the closure set to sessionDidFinishEventsForBackgroundURLSession on the delegate inside the Manager class, the message is printed after the warning.
When overriding sessionDidFinishEventsForBackgroundURLSession and printing inside of it before calling backgroundCompletionHandler, the message is printed after the warning.
As for verifying I have my Xcode project set up correctly for background sessions, I'm not sure how to do that and couldn't find any documentation on how to do so.
I should note that when trying to upload some screenshots from my phone I was initially unable to reproduce this issue in order to try these suggestions.
It was only after trying to share some photos that I was able to reproduce this again. I'm not sure or the correlation (if any) but it may be related to the photos taking longer to upload.
Update 2
The UIBackgroundModes are set exactly as #Nick described, and calling completionHandler() directly inside of application:handleEventsForBackgroundURLSession:completionHandler: does not display the warning.
Update 3
So, it appears I overlooked an small but important detail. There's a wrapper around Alamofire.Manager that doesn't expose it directly. The relevant part of its implementation looks like this:
private var manager: Manager
public var backgroundCompletionHandler: (() -> Void)? {
get {
return manager.backgroundCompletionHandler
}
set {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
}
and setting the completion handler in the AppDelegate executes that code path.
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.backgroundCompletionHandler = completionHandler
}
I've confirmed that the following change to expose the instance of Alamofire.Manager and access it directly does not produce the warning:
public var manager: Manager
// public var backgroundCompletionHandler: (() -> Void)? {
// get {
// return manager.backgroundCompletionHandler
// }
// set {
// manager.backgroundCompletionHandler = backgroundCompletionHandler
// }
// }
and in the AppDelegate:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.manager.backgroundCompletionHandler = completionHandler
}
Based on this it appears that using a computed property to proxy the completion handler is the cause of the issue.
I'd really prefer not to expose this property and would love to know of any workarounds or alternatives.
It appears as though everything you are doing is correct. I have an example app that does exactly what you've described that works correctly and does not throw the warning you are seeing. I'm guessing you still have some small error somewhere. Here are a few ideas to try out:
Verify the identifier matches the identifier of your background session
Add a didSet log statement on the backgroundSessionHandler in the Manager class temporarily to verify it is getting set
Add a log statement into the sessionDidFinishEventsForBackgroundURLSession to verify it is getting called as expected
Override the sessionDidFinishEventsForBackgroundURLSession on the delegate and manually call the backgroundSessionHandler
Verify you have your Xcode project set up correctly for background sessions
Update 2
Your computed property is wrong. Instead it needs to set the backgroundCompletionHandler to newValue. Otherwise you are never setting it to the new value correctly. See this thread for more info.

CoreBluetooth State Preservation and Restoration

I have the following scenario: iOS app (peripheral) X OSX app (central)
I instantiate my peripheral manager with CBPeripheralManagerOptionRestoreIdentifierKey.
In my peripheral's didFinishLaunchingWithOptions I send a local notification after getting a peripheral with UIApplicationLaunchOptionsBluetoothPeripheralsKey (don't do anything with it)
In my peripheral's willRestoreState I also trigger a notification (don't do anything other than that)
If my peripheral app is still running in the background before it gets killed due to memory pressure, I get messages from the OSX central just fine.
After the iOS app gets killed, when OSX central sends a message, both notifications mentioned above come through on iOS, but the message I was actually expecting doens't.
I've not resintantiated my peripheralManager at any moment, where and how should I do it? I only have one peripheralManager for the entire cycle of my app.
Any suggestions are welcome.
UPDATE:
if do
let options: Dictionary = [CBPeripheralManagerOptionRestoreIdentifierKey: "myId"]
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: options)
in willRestoreState, my apps just lose connection
Right, after having re-viseted all topics on this matter 100 times I've finally figured it out, here is exactly how it should be implemented:
in AppDelegate's didFinishLaunchingWithOptions:
if let options = launchOptions {
if let peripheralManagerIdentifiers: NSArray = options[UIApplicationLaunchOptionsBluetoothPeripheralsKey] as? NSArray {
//Loop through peripheralManagerIdentifiers reinstantiating each of your peripheralManagers
//CBPeripheralManager(delegate: corebluetooth, queue: nil, options: [CBPeripheralManagerOptionRestoreIdentifierKey: "identifierInArray"])
}
else {
//There are no peripheralManagers to be reinstantiated, instantiate them as you normally would
}
}
else {
//There is nothing in launchOptions, instantiate them as you normally would
}
At this point, willRestoreState should start getting called, however, there will be no centrals in your array of centrals if you have one to manage all centrals subscribed to your characteristics. Since all my centrals are always subscribed to all of my characteristics in one single service I have, I simply loop though all subscribedCentrals in any of the characteristics re-adding them to my array of centrals.
Please modify this according to your needs.
In willRestoreState:
var services = dict[CBPeripheralManagerRestoredStateServicesKey] as! [CBMutableService]
if let service = services.first {
if let characteristic = service.characteristics?.first as? CBMutableCharacteristic {
for subscribedCentral in characteristic.subscribedCentrals! {
self.cbCentrals.append(subscribedCentral as! CBCentral)
}
}
}
After this you should be ok to call any methods you have prepared to talk to any central, even if you deal with several of them simultaneously.
Good luck!

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.

Swift - CLLocationManager not asking for user permission

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.

Resources