I am putting together a simple estimote test using swift and coreLocation. However I am not getting didEnterRegion, didExitRegion
I read following answers already but it still didn't end my problem.
startMonitoringForRegion never calls didEnterRegion/didExitRegion
locationManager:didEnterRegion not called when a beacon is detected
I added Background modes (Location updates)
I get the "didStartMonitoringForRegion" log in the output but then don't get any didenter or exit. I tried to walk out of house with beacon and back in but no luck. Ranging however works.
var locManager: CLLocationManager = CLLocationManager()
let iceRegion: CLBeaconRegion = CLBeaconRegion(proximityUUID: BEACON_PROXIMITY_UUID, major: BEACON_ICE_MAJOR, identifier: "ice")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if locManager.respondsToSelector("requestAlwaysAuthorization") {
locManager.requestAlwaysAuthorization()
}
if (self.deviceSettingsAreCorrect())
{
iceRegion.notifyOnEntry = true
iceRegion.notifyOnExit = true
iceRegion.notifyEntryStateOnDisplay = true
locManager.delegate = self
locManager.startMonitoringForRegion(iceRegion)
locManager.startRangingBeaconsInRegion(iceRegion)
}
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
println("didStartMonitoringForRegion");
locManager.requestStateForRegion(region);
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
println("did Enter Region")
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
println("did Exit Region")
}
func locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) {
println("didDetermineState \(state)");
switch state {
case .Inside:
println("BeaconManager:didDetermineState CLRegionState.Inside");
case .Outside:
println("BeaconManager:didDetermineState CLRegionState.Outside");
case .Unknown:
println("BeaconManager:didDetermineState CLRegionState.Unknown");
default:
println("BeaconManager:didDetermineState default");
}
}
func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [CLBeacon]!, inRegion region: CLBeaconRegion!) {
println("BM didRangeBeacons");
for beacon: CLBeacon in beacons {
// TODO: better way to unwrap optionals?
if let major: String = beacon.major?.stringValue {
if let minor: String = beacon.minor?.stringValue {
println(major)
}
}
}
}
func deviceSettingsAreCorrect() -> Bool {
var errorMessage = ""
if !CLLocationManager.locationServicesEnabled()
|| (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.Denied) {
errorMessage += "Location services are turned off! Please turn them on!\n"
}
if !CLLocationManager.isRangingAvailable() {
errorMessage += "Ranging not available!\n"
}
if !CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion) {
errorMessage += "Beacon monitoring not supported!\n"
}
let errorLen = countElements(errorMessage)
if errorLen > 0 {
println(errorMessage)
}
return errorLen == 0
}
Related
I have a question about how one can connect a Search Bar with MapKit, so that it is able to search for places/ locations (not using StoryBoard). I have already written the code for the Search Bar and for the MapView in separate files, but even after trying literally every code and tutorial on the internet, I couldn't find a way to connect the Search Bar to search for locations. Below one can see respectively the used SearchBar.swift file, the MapViewController.swift and a snippet of the ContentView.swift.
SearchBar.swift
import UIKit
import Foundation
import SwiftUI
import MapKit
struct SearchBar: UIViewRepresentable {
// Binding: A property wrapper type that can read and write a value owned by a source of truth.
#Binding var text: String
// NSObject: The root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects.
// UISearchBarDelegate: A collection of optional methods that you implement to make a search bar control functional.
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
let Map = MapViewController()
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
text = ""
searchBar.showsCancelButton = true
searchBar.endEditing(true)
searchBar.resignFirstResponder()
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.showsCancelButton = true
searchBar.searchBarStyle = .minimal
//searchBar.backgroundColor = .opaqueSeparator
searchBar.showsCancelButton = true
return searchBar
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.text = text
}
}
MapViewController.swift
class MapViewController: UIViewController, CLLocationManagerDelegate {
let mapView = MKMapView()
let locationManager = CLLocationManager()
#Published var permissionDenied = false
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
checkLocationServices()
}
func setupMapView() {
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
guard let location = locations.last else { return }
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
let categories:[MKPointOfInterestCategory] = [.cafe, .restaurant]
let filters = MKPointOfInterestFilter(including: categories)
mapView.pointOfInterestFilter = .some(filters)
// Enables the scrolling around the user location without hopping back
locationManager.stopUpdatingLocation()
}
func checkLocalAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
permissionDenied.toggle()
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Show alert
break
case .authorizedAlways:
break
#unknown default:
fatalError()
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocalAuthorization()
} else {
// user did not turn it on
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocalAuthorization()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
The methods are then called in the ContentView.swift, using these methods:
struct MapViewRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
struct ContentView: View {
#State private var searchText : String = ""
var body: some View {
ZStack(alignment: .top) {
MapViewRepresentable()
.edgesIgnoringSafeArea(.all)
.onTapGesture {
self.endTextEditing()
}
SearchBar(text: $searchText)
}
}
}
Is it possible to connect both like I explained, or is there another method you advice? I really hope you guys can help me! Thanks in advance :)
Previously, I was using the following to discover e-mail meta-data from a drag & dropped e-mail(/-thread) from Mail.app.
if let filenames = draggingInfo.namesOfPromisedFilesDropped(atDestination: URL(fileURLWithPath: destinationDir!)) {
/// TODO: in future implementation Mail might return multiple filenames here.
/// So we will keep this structure to iterate the filenames
//var aPaths: [String] = []
//for _ in filenames {
if let aPath = pb.string(forType: "com.apple.pasteboard.promised-file-url") {
return aPath
}
//}
//return aPaths
}
Kind of janky, but it worked, since "com.apple.pasteboard.promised-file-url" was only supplied in those situations.
Since 10.12 however, the API seems to have changed, and looking at the WWDC2016 talk it appears that Apple wants us to use NSFilePromiseReceiver now.
I've tried a couple of approaches but I can't get a promised file URL to pop out.
Setup:
class DropzoneView: NSView {
var supportedDragTypes = [
kUTTypeURL as String, // For any URL'able types
"public.url-name", // E-mail title
"public.utf8-plain-text", // Plaintext item / E-mail thread title / calendar event date placeholder
"com.apple.pasteboard.promised-file-content-type", // Calendar event / Web URL / E-mail thread type detection
"com.apple.mail.PasteboardTypeMessageTransfer", // E-mail thread detection
"NSPromiseContentsPboardType", // E-mail thread meta-data
"com.apple.pasteboard.promised-file-url", // E-mail thread meta-data
"com.apple.NSFilePromiseItemMetaData" // E-mail thread meta-data
]
override func viewDidMoveToSuperview() {
var dragTypes = self.supportedDragTypes.map { (type) -> NSPasteboard.PasteboardType in
return NSPasteboard.PasteboardType(type)
} // Experiment:
dragTypes.append(NSPasteboard.PasteboardType.fileContentsType(forPathExtension: "eml"))
dragTypes.append(NSPasteboard.PasteboardType.fileContentsType(forPathExtension: "emlx"))
self.registerForDraggedTypes(dragTypes)
}
}
Handling:
extension DropzoneView {
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pasteboard: NSPasteboard = sender.draggingPasteboard()
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] else {
return false
}
var files = [Any]()
var errors = [Error]()
let filePromiseGroup = DispatchGroup()
let operationQueue = OperationQueue()
let newTempDirectoryURL = URL(fileURLWithPath: (NSTemporaryDirectory() + (UUID().uuidString) + "/"), isDirectory: true)
do {
try FileManager.default.createDirectory(at: newTempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
}
catch {
return false
}
// Async attempt, either times out after a minute or so (Error Domain=NSURLErrorDomain Code=-1001 "(null)") or gives 'operation cancelled' error
filePromises.forEach({ filePromiseReceiver in
filePromiseGroup.enter()
filePromiseReceiver.receivePromisedFiles(atDestination: newTempDirectoryURL,
options: [:],
operationQueue: operationQueue,
reader: { (url, error) in
Swift.print(url)
if let error = error {
errors.append(error)
}
else if url.isFileURL {
files.append(url)
}
else {
Swift.print("No loadable URLs found")
}
filePromiseGroup.leave()
})
})
filePromiseGroup.notify(queue: DispatchQueue.main,
execute: {
// All done, check your files and errors array
Swift.print("URLs: \(files)")
Swift.print("errors: \(errors)")
})
Swift.print("URLs: \(files)")
return true
}
Other attempts:
// returns nothing
if let filenames = pasteboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "com.apple.pasteboard.promised-file-url")) as? NSArray {
Swift.print(filenames)
}
// doesn't result in usable URLs either
if let urls = pasteboard.readObjects(forClasses: [NSPasteboardItem.self /*NSURL.self, ???*/], options: [:]) as? [...
Any pointers would be greatly appreciated.
I have managed to get the file to "pop out" but I cannot get the details for them. It transfers immediately and then hangs for 60 seconds before returning an error message.
Maybe it's a clue but the checkExtension method never returns unless commented out and set to true.
Hopefully this helps kick the can down the road a bit:
class DropView: NSView
{
var filePath: String?
required init?(coder: NSCoder) {
super.init(coder: coder)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.red.cgColor
registerForDraggedTypes([NSPasteboard.PasteboardType
.fileNameType(forPathExtension: ".eml"), NSPasteboard.PasteboardType.filePromise])
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
if checkExtension(sender) == true
{
self.layer?.backgroundColor = NSColor.blue.cgColor
return .copy
}
else
{
return NSDragOperation()
}
}
fileprivate func checkExtension(_ drag: NSDraggingInfo) -> Bool
{
return true
// guard let board = drag.draggingPasteboard().propertyList(forType: NSPasteboard.PasteboardType(rawValue: "com.apple.mail.PasteboardTypeMessageTransfer")) as? NSArray,
// let path = board[0] as? String
// else
// {
// return false
// }
//
// let suffix = URL(fileURLWithPath: path).pathExtension
// for ext in self.expectedExt
// {
// if ext.lowercased() == suffix
// {
// return true
// }
// }
// return false
}
override func draggingExited(_ sender: NSDraggingInfo?)
{
self.layer?.backgroundColor = NSColor.gray.cgColor
}
override func draggingEnded(_ sender: NSDraggingInfo)
{
self.layer?.backgroundColor = NSColor.gray.cgColor
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool
{
let pasteboard: NSPasteboard = sender.draggingPasteboard()
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] else {
return false
}
print ("Files dropped")
var files = [URL]()
let filePromiseGroup = DispatchGroup()
let operationQueue = OperationQueue()
let destURL = URL(fileURLWithPath: "/Users/andrew/Temporary", isDirectory: true)
print ("Destination URL: \(destURL)")
filePromises.forEach ({ filePromiseReceiver in
print (filePromiseReceiver)
filePromiseGroup.enter()
filePromiseReceiver.receivePromisedFiles(atDestination: destURL,
options: [:],
operationQueue: operationQueue,
reader:
{ (url, error) in
print ("Received URL: \(url)")
if let error = error
{
print ("Error: \(error)")
}
else
{
files.append(url)
}
print (filePromiseReceiver.fileNames, filePromiseReceiver.fileTypes)
filePromiseGroup.leave()
})
})
filePromiseGroup.notify(queue: DispatchQueue.main,
execute:
{
print ("Files: \(files)")
print ("Done")
})
return true
}
}
The output of this is a bit weird. The url variable aways repeats the name of the directory that I passed in eg
Files dropped
Destination URL: file:///Users/andrew/Temporary/
<NSFilePromiseReceiver: 0x6000000a1aa0>
** one minute gap **
Received URL: file:///Users/andrew/Temporary/Temporary/
Error: Error Domain=NSURLErrorDomain Code=-1001 "(null)"
["Temporary"] ["com.apple.mail.email"]
Files: []
Done
I saw this error when trying to receive promised files to an invalid destination url.
In my case I was using Ole Begemann's Temporary File Helper and accidentally letting it go out of scope, which deleted the directory before anything could be copied.
receivePromisedFiles gave me the -1001 timeout error after a long wait, but it did still pass a URL that would have been correct given my inputs. Obviously no file was at that location.
When I changed to a valid url all worked as expected. It might be worth checking Sandbox issues etc.
Apple now have some useful example projects in the File Promises section here:
https://developer.apple.com/documentation/appkit/documents_data_and_pasteboard
I have Parse class called Product that has 238 rows. Note that this class is not the Parse.com implementation of Product, it is a custom class implemented by myself, as I didn't require all the columns Parse adds to their Product class.
The Product class has a Pointer column (basically a foreign key in SQL tables), called ShopId, because each product belongs to a specific Shop (I have a Parse class called Shop with an ObjectId column used in the Product Pointer.
My Product class also has a File column called imageFile that holds the image of the product.
I want to download all Products from a specific shop, unpackage their image file and put it in my Swift Product class which consists of the PFObject of the Parse Product, and a UIImageView and a UIImage. Here is my Product Class in Swift:
class Product {
private var object: PFObject
private var imageView: MMImageView!
private var image: UIImage
init(object: PFObject, image: UIImage) {
self.object = object
self.image = image
}
func getName() -> String {
if let name = object["name"] as? String {
return name
} else {
return "default"
}
}
func setImageView(size: CGFloat, target: DressingRoomViewController) {
self.imageView = MMImageView(frame:CGRectMake(0, 0, size, size))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = self.image
imageView.setName(object["category"] as! String)
imageView.backgroundColor = UIColor.clearColor()
imageView.userInteractionEnabled = true
let tapGestureRecognizer =
UITapGestureRecognizer(target: target, action: "imageTapped:")
tapGestureRecognizer.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tapGestureRecognizer)
}
func getImageView() -> MMImageView {
return self.imageView
}
}
I am currently downloading all the products just fine, and getting their image file and creating my Swift Products with their images. However my UIProgressView logic is slightly off. I have the UIProgressView running for every product, every time I unpackage the product image. I need to shift the Parse.com ProgressBlock out of the getProduct swift function and into the loadProducts #IBAction. When I try it, it causes a lot of errors before compilation. How do I shift the ProgressBlock up to the loadProducts #IBAction? Here is my current code:
//
// ChooseShopViewController.swift
// MirrorMirror
//
// Created by Ben on 12/09/15.
// Copyright (c) 2015 Amber. All rights reserved.
//
import UIKit
import Parse
class ChooseShopViewController: UIViewController {
var progressView: UIProgressView?
private var allProducts: [Product] = []
private var categories: [ProductCategory] = []
#IBAction func loadProducts(sender: AnyObject) {
let shopQuery = PFQuery(className:"Shop")
shopQuery.getObjectInBackgroundWithId("QjSbyC6k5C") {
(glamour: PFObject?, error: NSError?) -> Void in
if error == nil && glamour != nil {
let query = PFQuery(className:"Product")
query.whereKey("shopId", equalTo: glamour!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
self.getAllProductsAndCategories(objects, error: error)
}
} else {
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Create Progress View Control
progressView = UIProgressView( progressViewStyle:
UIProgressViewStyle.Default)
progressView?.center = self.view.center
view.addSubview(progressView!)
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom") {
ShopDisplay.sharedInstance.setAllProducts(self.allProducts)
ShopDisplay.sharedInstance.setAllProductCategories(self.categories)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getAllProductsAndCategories(objects: [AnyObject]?, error: NSError?) {
if error == nil {
if let objects = objects as? [PFObject] {
for product in objects {
self.getCategory(product)
self.getProduct(product)
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
func getCategory(product: PFObject) {
if let category = product["category"] as? String {
var alreadyThere: Bool = false
for item in self.categories {
if category == item.rawValue {
alreadyThere = true
break
}
}
if alreadyThere == false {
self.categories.append(ProductCategory(rawValue: category)!)
}
}
}
func getProduct(product: PFObject) {
if let productImage = product["imageFile"] as? PFFile {
productImage.getDataInBackgroundWithBlock ({
(imageData: NSData?, error: NSError?) -> Void in
if let imageData = imageData {
let image = UIImage(data:imageData)
self.allProducts.append(
Product(object: product, image: image!))
}
if let downloadError = error {
print(downloadError.localizedDescription)
}
}, progressBlock: {
(percentDone: Int32) -> Void in
self.progressView?.progress = Float(percentDone)
if (percentDone == 100) {
//self.performSegueWithIdentifier("dressingRoom", sender: UIColor.greenColor())
}
})
}
}
}
I decided to not use the progressBlock, and instead to update my UIProgressView manually with a calculation. So here is the code. It's a little rusty. I could refactor now and maybe implement a calculated variable to make it cleaner. If my solution is a bad practice then I'm appreciative if that gets pointed out, and a better solution suggested (It doesn't seem good for performance to check the UIProgressView.progress value every iteration to perform the completion task of performing the segue).
import UIKit
import Parse
class ChooseShopViewController: UIViewController {
var progressView: UIProgressView?
private var allProducts: [Product] = []
private var categories: [ProductCategory] = []
static var numberOfProducts: Float = 0
#IBAction func loadProducts(sender: AnyObject) {
let shopQuery = PFQuery(className:"Shop")
shopQuery.getObjectInBackgroundWithId("QjSbyC6k5C") {
(glamour: PFObject?, error: NSError?) -> Void in
if error == nil && glamour != nil {
let query = PFQuery(className:"Product")
query.whereKey("shopId", equalTo: glamour!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
ChooseShopViewController.numberOfProducts =
Float((objects?.count)!)
print(ChooseShopViewController.numberOfProducts)
self.getAllProductsAndCategories(objects, error: error)
}
} else {
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Create Progress View Control
progressView = UIProgressView( progressViewStyle:
UIProgressViewStyle.Default)
progressView?.center = self.view.center
progressView?.progress = 0.00
view.addSubview(progressView!)
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom") {
ShopDisplay.sharedInstance.setAllProducts(self.allProducts)
ShopDisplay.sharedInstance.setAllProductCategories(self.categories)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getAllProductsAndCategories(objects: [AnyObject]?, error: NSError?) {
if error == nil {
if let objects = objects as? [PFObject] {
for product in objects {
self.getCategory(product)
self.getProduct(product)
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
func getCategory(product: PFObject) {
if let category = product["category"] as? String {
var alreadyThere: Bool = false
for item in self.categories {
if category == item.rawValue {
alreadyThere = true
break
}
}
if alreadyThere == false {
self.categories.append(ProductCategory(rawValue: category)!)
}
}
}
func getProduct(product: PFObject) {
if let productImage = product["imageFile"] as? PFFile {
productImage.getDataInBackgroundWithBlock ({
(imageData: NSData?, error: NSError?) -> Void in
if let imageData = imageData {
let image = UIImage(data:imageData)
self.allProducts.append(
Product(object: product, image: image!))
self.progressView?.progress += (100.00 /
ChooseShopViewController.numberOfProducts) / 100.00
print(self.progressView?.progress)
if self.progressView?.progress == 1 {
self.performSegueWithIdentifier("dressingRoom",
sender: UIColor.greenColor())
}
}
if let downloadError = error {
print(downloadError.localizedDescription)
}
})
}
}
}
I found this on the Parse website. It may be useful as it has a block that shows the percentage done that updates regularly during the download!
let str = "Working at Parse is great!"
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
let file = PFFile(name:"resume.txt", data:data)
file.saveInBackgroundWithBlock({
(succeeded: Bool, error: NSError?) -> Void in
// Handle success or failure here ...
}, progressBlock: {(percentDone: Int32) -> Void in
// Update your progress spinner here. percentDone will be between 0 and 100.
})
Did you find a better solution? besides this? I am trying to do something similar.
When it comes to iPhone devices, and using the map iPhone four can not handle
manager.requestAlwaysAuthorization()
But anything post iPhone4 requires that. How can I make it so the software will run successfully on both device types?
This worked
func triggerLocationServices() {
if CLLocationManager.locationServicesEnabled() {
if self.manager.respondsToSelector("requestWhenInUseAuthorization") {
manager.requestWhenInUseAuthorization()
} else {
startUpdatingLocation()
}
}
}
func startUpdatingLocation() {
manager.startUpdatingLocation()
}
// MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse || status == .Authorized {
startUpdatingLocation()
}
}
I am setting up an iOS 8 app to request Heath Kit Store authorization to share types. The request Read/Write screen shows fine and on selecting Done, I see the completion callback immediately after. In this callback, I am pushing a new view controller. I set a breakpoint for the code that is programmatically pushing the next view controller and this is called immediately, but the transition doesn't occur until about 10 seconds later.
Some code:
#IBAction func enable(sender: AnyObject) {
let hkManager = HealthKitManager()
hkManager.setupHealthStoreIfPossible { (success, error) -> Void in
if let error = error {
println("error = \(error)")
} else {
println("enable HK success = \(success)")
self.nextStep()
}
}
}
func nextStep() {
self.nav!.pushViewController(nextController, animated: true)
}
class HealthKitManager: NSObject {
let healthStore: HKHealthStore!
override init() {
super.init()
healthStore = HKHealthStore()
}
class func isHealthKitAvailable() -> Bool {
return HKHealthStore.isHealthDataAvailable()
}
func setupHealthStoreIfPossible(completion: ((Bool, NSError!) -> Void)!) {
if HealthKitManager.isHealthKitAvailable()
{
healthStore.requestAuthorizationToShareTypes(dataTypesToWrite(), readTypes: dataTypesToRead(), completion: { (success, error) -> Void in
completion(success, error)
})
}
}
func dataTypesToWrite() -> NSSet {
let runningType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let stepType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
return NSSet(objects: runningType, stepType)
}
func dataTypesToRead() -> NSSet {
let runningType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let stepType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let climbedType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierFlightsClimbed)
return NSSet(objects: runningType, stepType, climbedType)
}
}
Any thoughts on what is causing the time delay for the transition?
The problem was that the completion block is returned in the background queue. I just put the transition call back onto the main queue as follows:
hkManager.setupHealthStoreIfPossible { (success, error) -> Void in
if let error = error {
println("error = \(error)")
} else {
dispatch_async(dispatch_get_main_queue(), {
println("enable HK success = \(success)")
self.nextStep()
});
}
}
}