My app uses the typical pattern of starting a CLLocationManager and then calling stopUpdatingLocation from locationManager:didUpdateToLocation:fromLocation: if the newLocation is accurate enough. My question is whether I also need to call
[locationManager stopUpdatingLocation];
in locationManager:didFailWithError:. The Apple docs say
If the location service is unable to retrieve a location right away, it reports a kCLErrorLocationUnknown error and keeps trying. In such a situation, you can simply ignore the error and wait for a new event.
If the user denies your application’s use of the location service, this method reports a kCLErrorDenied error. Upon receiving such an error, you should stop the location service.
In the former case I shouldn’t call stopUpdatingLocation, since the location manager may still emit a good location. What about the other cases? My app always checks [CLLocationManager locationServicesEnabled] and [CLLocationManager authorizationStatus] before trying to use location services, so do I really need to handle the kCLErrorDenied case? And in the event of any other error, will location services be stopped automatically?
First off I think you should handle most error cases. Especially if it's as easy to handle as this one. ;-)
What happens if the user has granted access and uses your app. While the app is running she multitasks and changes into settings.app to disable location services; either generally or specifically for your app. Then you already checked for authorization and use location updates, but suddenly you aren't authorized anymore. I guess that's exactly the case you are asking about, right?
Related
I am developing a Mac application that prompts the user for files using the NSOpenPanel. The application is sandboxed (testing on OSX 10.9.4). I noticed that if I open a large amount of files (~3000), the open panel starts to emit errors to the log. This also happens if I try to open less amount of files in chucks for several times.
After the errors start to appear the first time, every time the NSOpenPanel is used again to open files, no matter for how many files, these errors will be generated again (until the application is closed).
The error message looks like this:
TestPanel[98508:303] __41+[NSSavePanel _consumeSandboxExtensions:]_block_invoke: sandbox_consume_fs_extension failed
One line for each file I try to open.
I managed to reproduce this behavior with a simple app: A sandboxed application with a single button invoking the following code:
NSOpenPanel* panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:YES];
[panel setCanChooseDirectories:NO];
[panel setCanChooseFiles:YES];
[panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
NSLog(#"%lu", [panel.URLs count]);
}];
The errors appear before the code reaches the completion handler.
It seems that I can still get the URLs from the panel in the completion handler but it really pollutes the system log.
EDIT:
Seems that this problem is not directly related to the NSOpenPanel/NSSavePanel panels. A very similar thing happens when using drap/drop with files. Something like this:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
...
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSURLPboardType]) {
NSArray *urls = [pboard readObjectsForClasses:#[[NSURL class]] options:nil];
}
...
}
This will generate the following log messages when dragging a large amount of files (the "magic" number seems to be somewhere around 2900):
Consume sandbox extension for itemIdentifier (2937) from pasteboard failed!
As with the NSOpenPanel, after the first occurrence of this, every single file dropped will generate the same error in the log.
EDIT 2:
#mahal tertin's reply pointed me to the right direction. The problem is indeed with the number of files and the fact that security scoped URL resources are limited.
However, there seems to be no reasonable solution found. The problem is that when the user clicks "OK" on the NSOpenPanel (or drops the files on a drag&drop aware control), behind the scenes the OS already attempts to create these security scoped URLs and implicitly calls startAccessingSecurityScopedResource for you. So if the user attempts to open more files than the limit, the resources are exhausted and the only option is to close and restart the application.
Calling stopAccessingSecurityScopedResource on the returned URLs seem to free the resources however this solution was discouraged by Apple's representative on the official developers forums (link is behind login).
It seems that the app is at the mercy of the user not to open too many files. And that is not even at once, since there is no approved way to release these resources. You can warn the user in documentation or even with an in-app alert but there is no way to prevent them from messing up the app and forcing a restart.
So if the app runs long enough and the user keeps opening files, the app will eventually become unusable.
Still looking for a reasonable solution for this.
After searching high and low and asking in various places, I am going to close this question and conclude there is no answer or solution to this. I am posting the known information on this for future reference.
All the solutions suggested are just workarounds that may minimize the problem and try to guide the user toward not trying to open too many files. But there nothing that can be done to actually solve this.
Here are the known facts about this issue:
No matter what you do, the user can attempt to open too many files in the NSOpenPanel dialog and exhaust the security scoped URL resources
Once these resources are exhausted, it is not possible to open any more files for reading/writing. The application needs to be closed and reopened
Even if the user doesn't attempt to open too many files at once, the application may still exhaust these resources if it runs long enough and the user opens enough files over time since startAccessingSecurityScopedResource is called automatically for files opened with NSOpenPanel (or the drag/drop mechanism) and nothing ever closes these resources
Calling stopAccessingSecurityScopedResource on all URL retrieved by the open panel will free these resources but this practice is discouraged by Apple, saying it might not be compatible with future solutions
When you receive the list of URLs from NSOpenPanel (or drag/drop), there is no way to tell if all URLs were successfully accessed or if there are URLs that are over the limit and therefore invalid.
Apple is aware of this and may fix it in the future. It is still not fixed in 10.10 and of course, that will not help current applications running on current/previous OSX version.
It seems Apple has really dropped the ball on this one, the Sandbox implementation seems very sloppy and short sighted.
The behavior you experience is because the security scoped resources are limited:
NSURL - (BOOL)startAccessingSecurityScopedResource tells
If sufficient kernel resources are leaked, your app loses its ability
to add file-system locations to its sandbox...
The current limit is roughly what you experienced. See:
What are the current kernel resource limits on security-scoped bookmarks?
To prevent it:
only start accessing those SSBs you need at a given time and subsequently stop accessing them
start access not files but enclosing folders: ask the user not to choose files but a full folder. This will grant you access to the whole tree beneath that directory
on draggingEntered: show a NSOpenPanel with the enclosing directory(ies) to grant access
In the process of learning iOS development and I am currently being taught how to use the core location framework.
I'm told that we need to create an instance of CLLocationManager, and then set a delegate, then implement this method:
-(void) locationManager: (CLLocationManager*)manager
didUpdateToLocation: (CLLocation*)newLocation
fromLocation: (CLLocation*)oldLocation
The book doesn't thoroughly explain how the location is actually received. From what I'm understanding, whenever locationManager logs a new location, it then sends a message (to the delegate?) with the selector being the above method, filling the parameters with the location data? Then we must implement this method and choose what to do with these parameters.
Is this correct? and if not, could someone explain to me exactly what is going on?
Thanks in advance, this is confusing me a ton.
Right, although the message you should implement starting in iOS 6 is -locationManager:didUpdateLocations:. After setting up the delegate, call -startUpdatingLocation and the Location Manager will start sending -locationManager:didUpdateLocations: (or the other method) whenever the location changes until you tell it to stop. Your implementation of that method an do whatever you like -- update a position on a map, log the location to a file, look up the nearest gas stations... There's some reason that you're asking for location updates, and whatever that reason is, this lets you do it.
I want to be able to notify the user if he entered the wrong username/password, or if for example the database is down. I am not sure if I need to do it in the didLoadResponse and just check that the response is not isOK or in the didFailLoadWithError.
Thanks
How you handle it depends on how you perform a login.
If you do basic authentication, by passing the username and password in the header of the request, then you'll get an error back from the service you're calling. And your delegate method, "objectLoader:didFailWithError:" method will get called. This method will most likely get called if there's a catastrophic problem on the backend, like the database being down.
If you have a separate webservice that performs a login operation, then it probably sends back a valid block, indicating whether the user-pass was valid or not. In this case, your "objectLoader:didLoadObject:" method probably got called, and you'll have to decipher the result appropriately.
Keep in mind that this behavior is totally controlled by what the back-end services do. If you can't talk directly with the people working on the services, then this may just be trial-and-error, and until you discover how those services work.
I am implementing in app purchase on iOS, where my server will provide access to certain content when the user purchases an item. I have everything working, including having the server able to verify the receipt from the purchase before providing access to the content.
However, if for some reason the app is not able to register the transaction with my server (due to a server outage, for example), I'd like to cancel the transaction so that the user isn't charged by iTunes. Is this possible?
To explain in further detail, the app contacts my server from within the paymentQueue:updatedTransactions function after the user clicks "buy." Currently, I call:
[_myQueue finishTransaction:transaction];
after contacting my server. However, if there was an error in contacting the server, is there a function I can call to cancel the transaction instead of finishing it?
I see from here that some people just don't call finishTransaction, so that the transaction gets restored the next time the user starts the app and the transaction observer is added (so the app can try to contact the server again) However, this is less than ideal because the user won't have access to the content until they restart the app and the server is available again, and yet the user will have already paid.
Thanks!
I think the only elegant way to achieve this is to have your server implement an availability check and call it as the last thing before you actually make the purchase. Once the user has confirmed the purchase via the UIAlertView that is presented to him, the purchase is made and you must do all that you can so that he gets his money's worth.
Since there is not a method to manually cancel a transaction, this is how I handle it: I do not mark the transaction finished, then I have the option of later manually calling the paymentQueue:updatedTransactions: like so:
if (SKPaymentQueue.defaultQueue.transactions.count > 0) {
//show a loading box to user
//...
//now manually call the observer method
[self paymentQueue:SKPaymentQueue.defaultQueue updatedTransactions:SKPaymentQueue.defaultQueue.transactions];
}
If my server call fails, then I present a UIAlertView with the option to Retry or Cancel. Retry calls the code above.
That way you do not have to wait until the app restarts, however if it does, that's ok too.
I'm working on a web application that enables users to login, only at specific locations. I'm using CLLocationManager and I initialized and called startUpdatingLocation at AppDelegate -didFinishLaunchingWithOptions. My UIWebView is supposed to pull the location at initialization to determine whether user's within the specified locations. However, at the launch of my application, [locationManager location] is always null and updates after my UIWebView is initialized and displayed therefore already sending an error message to the user.
How do I make my locationManager update its location at initialization?
Sounds like you've coded the location stuff correctly. What you are missing (but have seen) is that the update most certainly does not happen instantaneously. You need to "gate" the rest of your UI's presentation (i.e. your webview) on the location information becoming available. There are a lot of ways to do this. A common tactic is to present a full-screen "HUD" or veil with some indicator to the user that the app is initializing or locating them (with an activity indicator, too, is always a nice touch.) Behind that (out of sight to the user) you can be waiting for the location result and then kickoff the appropriate UI update, and then drop the veil.
Any of that make sense or give you some ideas? I've done this plenty of times. Synchronizing async activities (like location updates) with real-time UI updates that make sense can be challenging, but isn't impossible. :-)
You will need to account for an initial nil value in your applications UI and wait for the first location update.
-location
Discussion
The value of this property is nil if no location data has
ever been retrieved.
It is a good idea to check the timestamp of the location that is
returned. If the receiver is currently gathering location data, but
the minimum distance filter is large, the returned location might be
relatively old. If it is, you can stop the receiver and start it again
to force an update
Also you should check out Region Monitoring since you would like for you users to only be able to login at specific locations.