Check internet connection when user interacts - ios

Where I should implement a function to check internet connection on user's iPhone every time?
Exactly:
when some ViewController appears
when user goes to home screen and returns to app via multitasking
I have a lot of .swift files and I don't want to implement code in every viewWillApear function of every .swift file. Maybe there is some native place somewhere in AppDelegate or else?

Where I should implement a function to check internet connection on user's iPhone every time?
You shouldn't. The way to ask if the network is working is simply to try to use the network. You just attempt to do your networking and either you succeed or you fail in good order, and you take it from there. Most networking is done with URLSession, which will handle this gracefully for you, and in iOS 11 can even be configured to try again later automatically (waitsForConnectivity). Please watch the relevant WWDC 2017 video for further discussion.

Related

How can I know when it's safe to reuse a background NSURLSessionConfiguration id from another process?

I'm building an app where the user can initiate background file uploads from the app and a share extension. The user should be able to monitor the progress for any upload from the main app.
In the case where the upload is initiated from the extension I need to create a background session configuration with the same id that was used in the extension to get delegate calls to monitor progress and more in the app.
In the app I can not do this until the extension has exited.
Apple docs says https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html
You must create exactly one session per identifier (specified when you
create the configuration object). The behavior of multiple sessions
sharing the same identifier is undefined.
I've verified this. If I don't dismiss the share extension, I can create the session in the main app without any errors, but I don't receive any delegate calls.
When I dismiss the extension before switching back to the main app, I can attach to the same background session and I get delegate calls. All good.
When I dismiss the share extension using completeRequestReturningItems:completionHandler: in NSExtensionContext, when does the process exit?
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSExtensionContext_Class/#//apple_ref/occ/instm/NSExtensionContext/completeRequestReturningItems:completionHandler:
mentions
Calling this method eventually dismisses the app extension’s view
controller.
"Eventually" is not very specific. How can I know for sure from the main app that the extension isn't running?
The only workaround I've figured out is to periodically write a file in the shared container and make the main upp pick up the background session first after a timeout longer than that period. But it's an ugly hack.
If the upload is small, is it possible that the upload finishes while the extension process is running so that I must handle the completion of the upload in the extension instead of in the main app?
To summarize: how do you safely transfer an upload from an app extension to an app?
There's some great discussion of this class of problem by Quinn "the Eskimo!" on the old Dev Forums here.
The part directly relevant to you is
In my tests I've noticed that some annoying behaviour falls out of this design: if you start a task from an extension, it's non-deterministic as to whether the app or extension gets the didCompleteWithError callback. If the task runs super quickly, the extension typically gets the callback. If the task takes longer, the system has time to terminate the extension and the app is resumed to handle it.
There's really no way around this. The workaround is to put the code that handles request completion in both your app and your extension (possibly reusing the code via a framework).
It would be nice if the extension could disconnect from the session immediately upon starting its request. Alas, that's not currently possible (rdar://problem/18748008). The only way to programmatically disconnect from the session is to invalidate it, and that either cancels all the running tasks (-invalidateAndCancel) or waits for them to complete (-finishTasksAndInvalidate), neither of which is appropriate.
Sooooo,
is it possible that the upload finishes while the extension process is running so that I must handle the completion of the upload in the extension
Yes. Yes, it is. Don't you love "non-deterministic" behaviour?
how do you safely transfer an upload from an app extension to an app?
Sure sounds like you can't. So dupe that Radar there, then wait patiently for a fix!

Schedule UILocalNotification based on changes to Core Data

I'm making a simple app with a Today Widget extension that logs events.
The user can tap a button in the app or the related Today Widget to log an event. These events are saved with Core Data any time the button is pressed either place.
Whenever a new event is logged in the app, I run a function called updateLocalNotificationsFromCoreData(). It handles the setup of UILocalNotifications based on the most recent event in Core Data after clearing the appropriate existing notifications.
However, when a new event is logged from the Today Widget, I can't use this function because I need to register the Local Notification with UIApplication.sharedApplication().scheduleLocalNotification(), and UIApplication is not available in the Today Widget extension.
I realize I'll probably need do something unconventional or hacky to get this working, so I'm trying to evaluate possible approaches and come up with a relatively robust solution.
Basically, I want to find a way I can call my
updateLocalNotificationsFromCoreData() function right away any time a new event is logged.
If I can't do it every time an event is logged, an alternative would be to trigger the updateLocalNotificationsFromCoreData() function periodically (somewhat frequently) another way. Here are some solutions I was thinking about using, but I don't like any of them:
Do it in AppDelegate when the app is launched (or another state change)
One approach I'm thinking about is running my updateLocalNotificationsFromCoreData()function in AppDelegate somewhere, like didFinishLaunchingWithOptions.
The downside is that it would require the user to open the app periodically. If the user didn't open it much the notification behavior would be inconsistent. I'd prefer a solution where a user could interact with only the Today Widget and reliably get Local Notifications without ever opening the app.
Sync the events to a server and use Push Notifications
I've thought about syncing the data in Core Data to a server, then setting up Push Notifications to the user's phone based on that.
I don't like this, because I want the user to still be able to get notifications without an Internet connection. It also introduces a lot of extra overhead of syncing the data with a server.
Ping a server, and send a content-available Push Notification
When someone logs an event with the widget, I could ping a server. That server could send back a silent content-available push notification to trigger the app to run updateLocalNotificationsFromCoreData() in the background.
I found a similar question (Scheduling local notification from within a Today extension) where one answer proposes a similar solution. Unlike the previous solution, an Internet connection is not needed to receive the notifications, but an Internet connection would be required to make sure the notifications are up to date when a new event is logged.
Background fetch
I thought about using Background Fetch to fetch something arbitrary from a server, then run the updateLocalNotificationsFromCoreData(). This would be a way to trigger the update in the background, although it seems silly to fetch data if that data isn't being used, and seems like something for which an app could be rejected. There also seems to be a risk of the system not calling the background update regularly if the user doesn't open the app much and mostly uses the Today Widget.
Use background location updates
This seems like the dumbest approach, but I thought I would mention it anyway since I thought about it. I could use one of the low accuracy background location update modes to trigger updateLocalNotificationsFromCoreData().
It would require the user to allow location in the background, which would be hard to explain. And, it would require the user to at least move around a few blocks to trigger the function, which could provide an inconsistent user experience. Also, it would increase power consumption of the app for a silly reason.
I'd really appreciate fresh ideas about how I might be able to reliably schedule local notifications when Core Data changes on a device that doesn't have an Internet connection!
Or, if that doesn't seem possible, I'd appreciated feedback on which approach seems to make the most sense.
EDIT: I came up with a new solution. It's not ideal, but I think it's better than these other approaches I was considering. When someone taps the button to log the event, I launch the full app. It's annoying because I have all the data I need at that point to give the user feedback and log the event within the Today Widget without launching the app, but by launching the app I have the opportunity to check and schedule local notifications.
Also, in iOS 9 the annoyance on the user is slightly minimized because the system-wide "back" button will appear and let the user go back to the previous app easily once my app has launched from the Today Widget.
In the future I may try a solution where one of the server-based approaches above is used when an Internet connection is available, and I would then fall back to this system of opening the app only when the network connection is not available and I need to schedule the local notifications within the app.

How to detect in a jailbreak tweak AVCaptureSession start and get which app started it?

Basically, I want to know when a user is presented the camera feed and from which app this was triggered.
I tried to hook in AVCaptureSession's -(void)startRunning;
but
SBApplication *topApp = [(SpringBoard *)[UIApplication sharedApplication] _accessibilityFrontMostApplication];
is not working. It turns out that calling sharedApplication returns null. I've also tried calling the method on %c(SpringBoard) or objc_getClass("SpringBoard") but had the same result.
Besides this, the hook works only when opening the camera in the Messages app, although I specified in the Filters dictionary several bundles like com.apple.springboard, com.facebook.Facebook etc.
What am I doing wrong? Isn't AVCaptureSession API used by other apps ?
The SpringBoard class only exists in the SpringBoard process therefore %c(SpringBoard) or objc_getClass("SpringBoard") will return nil on any other process.
To get the current bundle name of the applications you are hooking, you can use [[NSBundle mainBundle] bundleIdentifier]. You'd have to use some form of IPC/XPC to send this information as well as additional information regarding the session that you want to know about. The receiving end could be either the SpringBoard process or a personal daemon so as to not clog SpringBoard with additional tasks.
As for your question "Isn't AVCaptureSession API used by other apps ?", it is possible that some applications use other classes to capture images/video rendering this hook useless. As a jailbreak developer I have found, as many others, that Apple programmers don't adhere to a strict coding style. There can also be other lower level functions that achieve the same purpose, in which case you would have more work to do as there is no class-dump parallel to C functions.
The most obvious thing would be to load your hook into every application. To achieve that you could use com.apple.UIKit as bundle filter. That way your hook will be loaded into every application where you would hook AVCaptureSession -(void)startRunning or UIImagePickerController methods to detect camera usage.
The problem with this - you have to implement some IPC mechanism to send notifications with user info from within sandboxed application. I don't know of any IPC API that could send those. Distributed notifications can't send user info with notifications from sandboxed app - it must be empty. Darwin notification center doesn't support user info. CFMessagePort also doesn't work. May be XPC does but I didn't tried it.
One possible solution is to find directory where even sandboxed app has write permissions (http://iphonedevwiki.net/index.php/Seatbelt). That way you could create a file in that directory with some data and send Darwin notification to your, let's say, daemon that will be observing these notifications. This daemon then will find the file and read the data you "attached" to your notification.
As for "lower level functions" to access camera. Basically, there are two ways you could access camera - AVCaptureSession and UIImagePickerController. Those are the only public APIs available for AppStore apps. If we are talking about cydia apps/tweaks, I don't think anyone would bother when you have AVFoundation APIs.

Should I listen for reachability updates in each UIViewController?

I see a lot of Reachability examples where people only display a message when reachability status changes.
But recently, I saw in Foursquare app that they display a message every time the user try to make an action requiring an Internet connection.
I think this is more robust and a better UX to remind the user he can't do anything without Internet. Mainly because users can switch between apps, do something else and forget he has no connection when he comes back.
Also as soon as they get the connection back I can see that they fetch data from the Internet and refresh the UI.
What I am really looking for is the best way to do this. How this is done?
Do they have a general UIViewController that checks for reachability each time it needs a connection?
Or do they have a kind of proxy class before each Internet request that cancels the request and display a message?
How you guys are dealing with that?
Thanks.
EDIT:
The solution I came up with is using AFNetworking which also provide reachability status in the box.
Basically I created an AFHTTPClient and set a reachability callback block on it to listen to status changes. The AFHTTPClient object is application wide (kind of a singleton). (in fact I have one AFHTTPClient per host I need to reach a.com, b.com ...).
Then when I need to perform a request I create a new AFHTTPRequestOperation (AFJSONRequestOperation in my case) and I enqueue it on my AFHTTPClient object.
In the failure block of the operation I check to see if the host is reachable with the networkReachabilityStatus property of the AFHTTPClient. If it's unreachable I display a message that there is no internet connection to the user.
I wrapped that up so I don't have to do this each time I create an operation. So now in the app, each time the user try to do something when there is no connection he got a message remembering him that he has no internet access.
I also use the reachability callback to reload data on a screen once I get the connection back (or rather once I am supposed to have a connection).
I don't know if it's best practice but I think it's nice to know that the app takes care of reloading important data as soon as a new connection is available.
If someone is interested by a sample code I can provide it.
On a WWDC talk this year the Apple engineer on stage recommended users to never base the application internet access on the Reachability example app status. Often reachability doesn't provide a complete information (it is based on a complex mechanism) and the suggestion provided by the engineer was this:
try to do your internet connection, whatever it is the Reachability status; then set your UI hint based on success/fail result
if it fails due to networking issue, then register to Reachability and retry again when Reachability gives the green light; this is needed when you want to recover automatically from the fail condition
in any case give the user the possibility to "force a retry", whatever is the Reachability status. If it succeeds, reset your UI hint immediately.
What the Apple engineer said is perfectly true: quite often you can see in the console log Reachability failure messages while the internet connection is perfectly alive.
Other thing: there is no better "network hint" than the one displayed in the status bar: if you have there the wi-fi icon, the 3G/4G icon, the cellular field strength.
Returning to your original question: there is no absolute better way to manage this stuff, this depends heavily on the application architecture. In case you prefer to concentrate your networking stuff in a dedicated class (not a UIViewController but a NSObject subclass) then it could make sense to define a read-only property for that class that is updated with "success/fail" after latest internet connection with the servers app (it makes no sense to ping other servers like Google or Apple: first of all it's not elegant, second the issue may come from your the servers that serve the app and not the device internet connection status!). #property (readonly) BOOL lastConnectionToMyServerSuccess
Then your view controllers can register (by KVO or by central notification) to this property changes and update their UI accordingly by showing an icon or else (I repeat: leave the user the possibility to try manually to connect to the internet). View controllers should unregister from KVO when out of sight ("viewWillDisappear:") or unloaded ("viewDidLoad:") or dealloc'd.
Of course this adds some extra complexities. E.g.: you use the app, the internet light was green. Then you suspend it, do something else and after a few minutes your return to the app. In such case the app should ping your servers to restore the internet light status again, as after a few minutes network conditions could have changed (e.g. you are on a train). In any case all loaded view controllers will get the KVO notification from the network dedicated class and update themselves.

iOS app upload data to server as soon as network is available

I have an app that tracks wildlife where the user enters data based on their observations (eg. user enters they see 3 moose). The data is then uploaded to a server. However, because this app will be used out in the field where there is often no internet connection, I want to save data if there is no connection, and upload the data as soon as the network is available
I know about Reachability, but it looks like I can only check if the internet connection is available at that moment, and doesn't check in the background for internet connectivity
So to summarize:
If there is an internet connection when users submits data, then that's fine.
If there is no internet connection, when user submits data, I want to save this data. As soon as there is an internet connection, I want the data to be uploaded, without needing the user to open up the app again. I can't rely on the user to open the app again causing the data to be submitted, because they will likely only use this app out of the range of cell towers, and will likely NEVER run the app in a location with a network connection, so it would have to automatically submit this data for them.
Looking around, I can't find an answer so I'm beginning to wonder...is this even possible?
No, Apple don't allow applications to run indefinitely in the background for this purpose, and they don't allow applications to be triggered remotely or anything of that nature. At best you could have your application run in the background to get notifications about major location changes, but you'd have to have it as a proper feature rather than a hack to get around this limitation, otherwise your application won't get approved by Apple.
I know it's possible to utilize the network in the background but only for a limited time after the user closes the app. You could create a timer which checks for a network connection (using Reachability or by pinging Google) and set the timer to fire every minute after the app closes. It's not a very efficient solution but it may work. You should look into how long you can maintain a connection after the app close though, I think it is 5-10 minutes.

Resources