Location authorization error even though I've asked for authorization in iOS 8 using swift - objective-c

I have an app using the location services. If the app first starts, it ask the user for permission.
For some reason, if I tap on "Allow" I'll get this message:
Trying to start MapKit location updates without prompting for location authorization.
I know what this means, but I've set breakpoints all over my code and I am SURE that nothing tries to read the user location before it is allowed to do so.
Anyway, I seem to be missing something.
1) Is there a "common mistake" which one could do, something within the storyboard or so?
2) Will Apple reject an app that has such an error?
Thing is that the app works perfectly well, The only thing is that I see this message within the console. I don't know whether Apple will see this message too and if this would be a reason to reject an app..

if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) { // iOS8+
// Sending a message to avoid compile time error
[[UIApplication sharedApplication] sendAction:#selector(requestWhenInUseAuthorization)
to:self.locationManager
from:self
forEvent:nil];
} else {
[self.locationManager startUpdatingLocation];
}
}
I think you may need to include something like this for the requestWhenInUseAuthorization

Assume that you are using CLLocationManager. So did you make a strong reference to your locationManager object?
It seems to be a case when you requested location in local scope (variable) inside a function. Then trying to use MapKit, but locationManager object is already deallocated.
To solve that case, you should declare...
var locationManager = CLLocationManager()
... as an instance variable, then request authorization, and then using location services.

The problem was I have asked for authorization to use location services if the app is in use and I have determined the authorization like this:
func locationManager(manager: CLLocationManager!,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = GpsStatus.restricted
break
case CLAuthorizationStatus.Denied:
locationStatus = GpsStatus.denied
break
case CLAuthorizationStatus.NotDetermined:
locationStatus = GpsStatus.notDeterminded
break
default:
locationStatus = GpsStatus.allowed
break
}
}
This seems to be wrong, the error is gone if I explicitly check for CLAuthorizationStatus.AuthorizedWhenInUse:
func locationManager(manager: CLLocationManager!,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = GpsStatus.restricted
break
case CLAuthorizationStatus.Denied:
locationStatus = GpsStatus.denied
break
case CLAuthorizationStatus.NotDetermined:
locationStatus = GpsStatus.notDeterminded
break
case CLAuthorizationStatus.AuthorizedWhenInUse:
locationStatus = GpsStatus.allowed
default:
locationStatus = GpsStatus.notDeterminded
break
}
}
EDIT:
Seems like it's also a problem to add a TrackingLocationButton before I have the permission to use location services.
So do this
var userTrackingButton = MKUserTrackingBarButtonItem(mapView: self.mapView);
self.toolBar.items?.insert(userTrackingButton, atIndex: 0)
only if you have the permission

For iOS 8 you need to define the "Privacy - Location Usage Description" in the Info.plist.
Eg. NSLocationWhenInUseUsageDescription = "Use your location to show near by stores".
or
NSLocationAlwaysUsageDescription = "Use your location to show near by stores".
This key specifies the reason for accessing the user’s location information.

Related

Having Trouble Getting the UIDocumentBrowserController to open docs in a Document based app

I've been working on a new Document-based app, and was super glad about the new UIDocumentBrowserController...trying to roll my own solution for the document browser UI was tricky!
I'm having some trouble getting the browser to open documents after they've been created.
What happens now is that when I choose to create a new document in the document browser, the document is created and opened as expected, although an error message is logged. However, after the doc is closed, I cannot reopen the file, either immediately or upon subsequent launches, even though the document is displayed. However, a weird clue here is that if I stop running the app after creating the document, but without adding new information to it (triggering the save cycle), and run the project again, I can open the file correctly. Whuch makes me think that there's something in the way the files are being saved that is the issue.
(Note: At this phase, I'm working on getting the local, non/icloud implentation working, before I move on to the icloud implementation.)
Here is the error message at any point in the code whenthe document is saved to disk (or at least most of the time!):
2017-06-20 13:21:58.254938-0500 Sermon Design 2 iOS[22454:5000138] [default] [ERROR] Could not get attribute values for item file:///Users/stevenhovater/Library/Developer/CoreSimulator/Devices/9A4364F2-B3A1-4AD9-B680-FB4BC876C707/data/Containers/Data/Application/DD534ED8-C4A3-40FE-9777-AED961976878/Documents/Untitled-9.sermon. Error: Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL." UserInfo={NSLocalizedDescription=The reader is not permitted to access the URL.}
I suspect that the issue lies somewher in my document types plists, which I've tried to set up by imitating the setup in the video for wwdc 2017 session 229.
My docs are encapuslated by an NSData object, using what I take to be a pretty standard subclass implentation of UIDocument. (I'm omitting the code to generate the thumbnails)
override func contents(forType typeName: String) throws -> Any {
print("Saving Document Changes")
if sermon != nil {
let newData = NSKeyedArchiver.archivedData(withRootObject: sermon!)
return newData
} else {
let newData = NSKeyedArchiver.archivedData(withRootObject: Sermon())
return newData
}
}
override func fileAttributesToWrite(to url: URL, for saveOperation: UIDocumentSaveOperation) throws -> [AnyHashable : Any] {
let thumbnail:UIImage = self.createThumbnail()
let thumbnaildict = [URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey : thumbnail]
let dict = [URLResourceKey.thumbnailDictionaryKey:thumbnaildict]
return dict
}
override func load(fromContents contents: Any, ofType typeName: String?) throws {
guard let newSermon:Sermon = NSKeyedUnarchiver.unarchiveObject(with: contents as! Data) as? Sermon else{
throw documentErrors.invalidFile
}
self.sermon = newSermon
}
In my subclass of UIDocumentBrowserViewController, Here is my code for getting a local filename and for creating the new document.
func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: #escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) {
var newDocumentURL: URL? = nil
print("creating new local document")
guard let target = self.newLocalFilename() else {
return
}
let targetSuffix = target.lastPathComponent
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory() + targetSuffix)
let newDocument:SDDocument = SDDocument(fileURL: tempURL)
newDocument.sermon = Sermon()
/
newDocument.save(to: tempURL, for: .forCreating) { (saveSuccess) in
/
guard saveSuccess else {
/
importHandler(nil, .none)
return
}
/
newDocument.close(completionHandler: { (closeSuccess) in
/
guard closeSuccess else {
/
importHandler(nil, .none)
return
}
/
importHandler(tempURL, .move)
})
}
}
func newLocalFilename() -> URL? {
let fileManager = FileManager()
guard let baseURL = self.localDocumentsDirectoryURL.appendingPathComponent("Untitled")
else {return nil}
var target = baseURL.appendingPathExtension(DocumentBrowserViewController.documentExtension)
var nameSuffix = 2
while fileManager.fileExists(atPath: target.path) {
target = URL(fileURLWithPath: baseURL.path + "-\(nameSuffix).\(DocumentBrowserViewController.documentExtension)")
nameSuffix += 1
}
let targetSuffix = target.lastPathComponent
print("Target name: \(targetSuffix)")
print("new url: \(target)")
return target
}
After four or five hours of work banging my head against this problem, I discovered a simple solution: don't test in the Simulator. I switched to testing on my device and instantly everything started working as advertised.
[I can't speak from experience here, but it may be that the "doesn't work in the Simulator" problem is confined to Sierra, but that the Simulator does work in High Sierra. This would explain why some users see this issue and others don't, and especially why Apple seems blissfully unaware of it in the WWDC video.]
I had exactly the same issue when I was trying to save to NSTemporaryDirectory().
If you instead save to the documents directory ([[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]), it appears to work fine!
Update: it looks like this issue is fixed in iOS 11 beta 3, and you can now save newly created documents to NSTemporaryDirectory() correctly.
Here is my current theory.
This error
Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL."
shows up when first creating a UIDocument at a new URL using -initWithFileURL. It's basically saying "this URL doesn't exist yet," but in a way that makes it sound more like a permissions issue.
As far as I can tell, it doesn't prevent you from saving, opening, editing, or closing the file. So I think it's just a superfluous error that Apple should have taken out.
I found that the error happens on simulator when LSSupportsOpeningDocumentsInPlace property is set to YES in the info.plist.
Turn this property to NO, then it starts working, in my case.
On the real device, it works anyway without having error.
Had similar problem, and tried the method for providing a default Core Location in the Schemes settings and it works now. The method was mentioned in this answer: IOS 9 Error Domain=kCLErrorDomain Code=0 "(null)"

XCode UITesting check if a text field exists

I can't find any way to check if a text field exists without trying to get it which then fails the tests and shows an error if it can't be found. No matches found for TextField
Current code
XCUIElement *usernameTextField = app.textFields[#"username"];
Reason/detail
I've got a Objective C UITest in XCode which logs into my app in setUp and logs out in tearDown however sometimes my app is already logged in when the test starts (if the simulator has been used for anything else in the meantime). I'd like to be able to check to see if the username textfield exists in my setUp and then if it doesn't I can skip the login or call my logout function and continue as normal.
Not sure about Obj-C but here's how it would work in Swift.
let usernameTextField = app.textFields["username"]
if usernameTextField.exists {
do something
} else {
do something else
}
Here is the code in Swift, which can easily be converted to Obj-C:
// given:
// usernameTextField exists
// The username that is possibly entered there is "username".
// then:
if usernameTextField.value as! String == "username" {
// logged in
} else {
// not logged in
}

ReactiveCocoa binding "networkActivityIndicator" Crushes

I have this code:
RAC(self.viewModel , password) = self.signupCell.passwordTextField.rac_textSignal;
RAC(self.viewModel , userName) = self.signupCell.usernameTextField.rac_textSignal;
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = self.viewModel.executeRegister.executing;
At my LogIn page.
At first is runs perfect, But it user Logout and gets to the register page once again, the app crushes at the line:
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = self.viewModel.executeRegister.executing;
With Error:
'Signal name: is already bound to key path "networkActivityIndicatorVisible" on object , adding signal name: is undefined behavior'
I'm guessing it has something to do with subscribing to UIApplication events. But I'm not sure what else can i do beside sending subscriber completed as so:
[subscriber sendCompleted]
Any one had the same problem?
thanks.
EDIT
With the help of #erikprice and #powerj1984 I found a solution:
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = [self.viewModel.executeRegister.executing takeUntilBlock:^BOOL(id x) {
return _viewShowing;
}];
The "_viewShowing" veritable is setted to YES on ViewWillAppear, And to NO on ViewWillDisapear.
This is not the best coding.. So if anyone has a better option i would be happy to use it.
Thanks.
That error message means that you're trying to call RAC(UIApplication.sharedApplication, networkActivityIndicatorVisible) more than once. Make sure you only make that call on that specific property of that specific object one time, ever. (Or at least until such time as you dispose of the subscription, as #powerj1984 suggests.)

Firebase OSX simple login with email/pass oddity

I'm setting up a very basic OSX app for an existing firebase with SimpleLogin and email/password authentication.
Here's the code I'm using.
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
Firebase* ref = [[Firebase alloc] initWithUrl:#"https://myapp.firebaseio.com"];
FirebaseSimpleLogin* authClient = [[FirebaseSimpleLogin alloc] initWithRef:ref];
[authClient loginWithEmail:#"myemail#mydomain.com" andPassword:#"mypassword"
withCompletionBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// There was an error logging in to this account
NSLog(#"authClient login error: %#", error);
} else {
NSLog(#"Login success.");
}
}];
}
Login is successful, and I see the log output. However, "FAUser* user" is nil. How? Why?
Online search / existing SO questions haven't helped..
Any ideas?
** UPDATE **
Same code in iOS works as expected. Is this just an OSX issue?
** UPDATE 2 **
I compiled the source code from the Firebase/Objective-C Simple Login Service (which seems to only reference iOS) directly in my OSX project and found that there is a "duplicate item" error code when the login service tries to store Keychain data on OSX.
The source code after the keychain save operation then proceeds to return a null user. I believe there is a logic error here because the if statement evaluates to true whenever it is not a success code (skipping the special case for duplicate item):
if (status != noErr) {
user = nil;
}
else if (status == errSecDuplicateItem) {
// TODO: log an error?
user = nil;
}
Anyway, I am able to continue working by modifying this small chunk of code to fit my needs.
Yeah, you're right. It's an error with accessing Keychain.
To clear the error on your own machine, you can go to Keychain Access, search for 'firebase' in the upper right. You should get an item with the name https://auth.firebase.com. Click it. Check that it says Firebase_<YOUR_FIREBASE_NAME> for the Account field, and go ahead and delete it. The next time you spin up your app, you'll have to log in again.
This seems to show up when you try to hit the same keychain item many times at once. Accessing the keychain slowly multiple times seems to be fine, so unless someone is authenticating many of the same app on the same machine, you shouldn't get the error. (If you can hit it some other way, please share!)

How is it better to wait an asynchronous method to be finished in iPhone app?

everybody.
I want to understand, how i shoud procceed situations when an asynchronous method has "didFinish:#selector(SEL)" parameter.
My code example is:
//
// Authentication check
- ( void )authenticationSuccess: ( GDataServiceTicket* ) ticket
authenticatedWithError: ( NSError* ) error {
if ( error == nil )
{
NSLog( #"authentication success" );
}
else
{
NSLog( #"authentication error" );
}
}
//
- ( void ) fetchFeedOfSpreadsheets {
//create and authenticate to a google spreadsheet service
if ( !(mService) )
{
GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
[mService autorelease];
mService = [service retain];
}
// check autentication success ( invoke "authenticationSuccess" method for debug success & error )
[mService authenticateWithDelegate: self
didAuthenticateSelector:#selector(authenticationSuccess:
authenticatedWithError:) ];
// HERE I WANT TO MAKE A PAUSE AND WHAIT THE RESULT, EITHER I AUTHENTICATED OR NOT
// AND MAKE AN "IF" STATEMENT TO CONTINTUE WORKING ON SERVER, OR RETURN ERROR
//fetch retrieves the feed of spreadsheets entries
NSURL *feedURL = [ NSURL URLWithString: kGDataGoogleSpreadsheetsPrivateFullFeed ];
GDataServiceTicket *ticket;
ticket = [mService fetchFeedWithURL: feedURL
delegate: self
didFinishSelector: #selector(spreadsheetsTicket:finishedWithFeed:
error: ) ];
// HERE I WANT TO WAIT SECOND TIME. I WANT "spreadsheetsTicket:
// finishedWithFeed:error:" TO PROCCEED ERROR AND PUT A FEED IN SOME NSARRAY OBJECT
// AND AFTER THAT I WANT TO WORK WITH THAT NSARRAY RIGHT HERE
}
I's clear, that i can push the code i want into the end of "authenticationSuccess" method section, but it's also clear, that it's a wrong a way to solve the proble. There a number of situations like this, where i call an asynchronous method with a selector parameter, and i want to find a solution providing me a flexible code writing.
Thanks in advance.
It's a standard practice in Objective-C to put the code to be executed after the authentication in the authenticationSucess: method. You might not like it, but that is life.
Many people had the same complaint as you, so
on iOS 4 and later, there's something called blocks which allow you to write the code to be executed after the authentication in the method which initiates the authentication, as in
[mService authenticateAndExecute:^{
code to be executed when successfully authenticated ;
} whenError:^{
code to be executed when authentication failed;
} ];
But in this case you need to modify the API, which is possible by using categories. See this blog post by Mike Ash. He has many other posts on blocks on the same blog, which are also very instructive.
If you're going to use a library that works asynchronously (and therefore doesn't block your UI), you should have a good reason for trying to force it to work synchronously.
You should be checking for an authentication error at the end of your authenticationSuccess:authenticatedWithError: method, and calling the next request from there if there's a success. Similarly, in your spreadsheetsTicket:finishedWithFeed:error: check for an error, and continuing processing if there isn't one. It might be a better design to do that continued work in a separate method, but that's up to you.
Is there a specific reason you want to use the GData API in a synchronous fashion?