In an old, Objective-C based project I have been using the below code to detect if the iOS devices is currently connected using Wifi (not cellular).
My attempts to translate this code into Swift 5 failed due to the Objective-C pointers. Is there a clean way to use this solution in Swift?
Or are are there better ways to solve this nowerdays? I found solutions using the Reachability port to Swift or NWPathMonitor(). While they seem to work in general, these solution are used to monitor the connection state and send notifications on changes while one time checks are not (well) supported.
Event though these solution could be used to get the current connection state, this is done using delegate callback methods or closures. Thus it is not possible to use these solutions in existing code which was created to work "synchronously" (without callbacks/closures).
Is there a simply way to use localWiFiAvailable in Swift?
The code:
+ (BOOL)localWiFiAvailable {
struct ifaddrs *addresses;
struct ifaddrs *cursor;
BOOL wiFiAvailable = NO;
if (getifaddrs(&addresses) != 0) return NO;
cursor = addresses;
while (cursor != NULL) {
if ((cursor -> ifa_addr -> sa_family == AF_INET) && !(cursor -> ifa_flags & IFF_LOOPBACK)) { // Ignore the loopback address
// Check for WiFi adapter
#if TARGET_IPHONE_SIMULATOR
wiFiAvailable = true;
break;
#else
if (strcmp(cursor -> ifa_name, "en0") == 0) {
wiFiAvailable = YES;
break;
}
#endif
}
cursor = cursor -> ifa_next;
}
freeifaddrs(addresses);
return wiFiAvailable;
}
Details on why NWPathMonitor() cannot be used:
As #baronfac pointed out in his comment NWPathMonitor() can also deliver the current state, but this can only be done using its .pathUpdateHandler closure.
I am using a third-party library where I can override a souldSendData() -> Bool method. Sending the data should not be allowed on mobile connection but only on WiFi. The methodes requires an instant decision to return true or false. Waiting for the closure is thus not possible.
So, I am limited by the existing class here. Yes, connection could change any second, however this is a different problem. e.g. NWPathMonitor can be used to cancel the transfer when connection changes to mobile.
Solving this problem in Objectiv-C was no problem using the code shown above. The question is simply, if such a "direct" solution is possible in Swift as well. While using the Objectiv-C code in the Swift project would be possible I would prefer to keep the project Swift only.
As mentioned by Paulw11, the recommended approach is using NWPathMonitor. A common practice is the following within a UIViewController - class:
private var monitor: NWPathMonitor?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { [weak self] path in
if !path.isExpensive { // this means the device is connected via WiFi
// enter your code here
}
}
let queue = DispatchQueue(label: "Monitor")
monitor?.start(queue: queue) // start to monitor the connection
}
override func viewWillDisappear(_ animated: Bool) {
monitor?.cancel() // end to monitor the connection
super.viewWillDisappear(animated)
}
EDIT:
Thanks to FLichter and Rob Napier for the clarification. Maybe it helps to use this approach:
func shouldSendData() -> Bool {
let monitor = NWPathMonitor()
return !monitor.currentPath.isExpense
}
Related
I'm trying to write a macOS app that would connect to already paired the bluetooth phone and retrieves the list of address book entries and call records. This information should be available via standard OBEX interface. I'm relatively new to macOS development (although have enough experience with iOS development) and I have a feeling that I'm doing something wrong on a very basic level.
Here are snippets of my code:
First I'm finding particular paired Bluetooth device by its address
let paired = IOBluetoothDevice.pairedDevices()
let device = paired?.first(where: { (device) -> Bool in
return (device as? IOBluetoothDevice)?.addressString == "some_address"
}) as? IOBluetoothDevice
This actually works fine and I'm getting back valid object. Next, I'm picking up address book service and creating BluetoothOBEXSession for it
let service = device!.getServiceRecord(for: IOBluetoothSDPUUID(uuid32:kBluetoothSDPUUID16ServiceClassPhonebookAccess.rawValue))
let obexSession = IOBluetoothOBEXSession(sdpServiceRecord: service!)
This also works fine, I'm getting proper service object and session is created.
Next step (I would assume) is to create an OBEXFileTransfer session and do something (like checking current directory or retrieving the content of telecom/cch which supposed to have the list of combined outgoing and incoming calls:
let ftp = OBEXFileTransferServices(obexSession: obexSession!)
ftp!.delegate = self
if ftp!.connectToFTPService() == 0 {
NSLog("\(ftp!.currentPath())") // -- empty
ftp!.changeCurrentFolderForward(toPath: "telecom/cch")
NSLog("\(ftp!.currentPath())") // -- empty
ftp!.retrieveFolderListing()
}
I have added the following delegate's method to my view controller (to receive callbacks from OBEX FTS but they never get called:
override func fileTransferServicesRetrieveFolderListingComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError, listing inListing: [Any]!) {
NSLog("Listing complete...")
}
override func fileTransferServicesConnectionComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Connection complete...")
}
override func fileTransferServicesDisconnectionComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Disconnect complete...")
}
override func fileTransferServicesAbortComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Abort complete...")
}
What am I doing wrong here?
I also could not find any good Bluetooth examples for macOS either, if somebody has good links, please do share.
I have an iOS app that makes some small network requests on app launch (resource updates, etc). If the user turns off cellular access for the app in iOS Settings, they get a prompt from iOS about network usage every time they launch. Is there a way to know programmatically that cellular data for this app has been disabled, so that I can disable the requests at startup?
So I found this on the apple dev forums from an Apple engineer (https://devforums.apple.com/message/1059332#1059332).
Another developer wrote in to DTS and thus I had a chance to
investigate this in depth. Alas, the news is much as I expected:
there is no supported way to detect that your app is in this state.
Nor is there a way to make a "no user interaction" network connection,
that is, request that the connection fail rather than present UI like
this. If these limitations are causing problems for your app, I
encourage you to file a bug describing your specific requirements.
https://developer.apple.com/bug-reporting/
So it looks like it is not possible to detect if cellular data for your app has been turned off.
Edit
I filed a radar for this requesting that it be added. I just got this notification in my radar
We believe this issue has been addressed in the latest iOS 9 beta.
I looked through the API diffs, but so far I can't find the new API.
As of iOS9, the capability to check the setting to enable/disable use of cellular data for your app (Settings/Cellular/AppName) is available using Apple's CTCellularData class. The following code will set cellularDataRestrictedState when it is run initially and then set it and log whenever it changes:
import CoreTelephony
var cellularDataRestrictedState = CTCellularDataRestrictedState.restrictedStateUnknown
let cellState = CTCellularData.init()
cellState.cellularDataRestrictionDidUpdateNotifier = { (dataRestrictedState) in
if cellularDataRestrictedState != .restrictedStateUnknown { // State has changed - log to console
print("cellularDataRestrictedState: " + "\(dataRestrictedState == .restrictedStateUnknown ? "unknown" : dataRestrictedState == .restricted ? "restricted" : "not restricted")")
}
cellularDataRestrictedState = dataRestrictedState
}
Unfortunately (as of iOS11) this seems to check only the state of the app's switch - if your app's switch is set to enabled and the user switches the Cellular Data master switch to disabled, this API will return the app's state as being "not restricted".
Just wanted to add an Objective C version of the above Swift code for future travellers.
- (void)monitorCanUseCellularData {
if (GCIsiOS9) {
CTCellularData *cellularData = [[CTCellularData alloc] init];
NSLog(#"%ld", cellularData.restrictedState);
// 0, kCTCellularDataRestrictedStateUnknown
[cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
NSLog(#"%ld", state);
self.canUseCellularData = cellularData.restrictedState ==2?true:false;
}];
}
}
I have found that the CTCellularData class needs some time to get to the correct value. In my implementation I call the didUpdateNotifier very early after appDidFinishLaunching. By the time my networking call are returning with errors I definitely have a correct value for the restricted state.
class CellularRestriction: NSObject {
private static var cellularData = CTCellularData()
private static var currentState = CTCellularDataRestrictedState.restrictedStateUnknown
static var isRestricted: Bool {
currentState = cellularData.restrictedState
return currentState == .restricted
}
static func prepare() {
if currentState == .restrictedStateUnknown {
cellularData.cellularDataRestrictionDidUpdateNotifier = { state in
currentState = cellularData.restrictedState // This value may be inconsistent, however the next read of isRestricted should be correct.
}
}
}
}
You can detect if cellular data disabled using NWPathMonitor class. (https://developer.apple.com/documentation/network/nwpathmonitor)
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
cellMonitor.pathUpdateHandler = { path in
self.isCellConnected = path.status == .satisfied
}
Adding to dirkgroten's answer, you can use the Apple Reachability class, found here:
https://developer.apple.com/Library/ios/samplecode/Reachability/Introduction/Intro.html
It uses SCNetworkReachability, and is very straight forward to use, it will detect connectivity via Cell and WiFi as you will need to check both at start up.
There are lots of frameworks out there that will give you the status of your network connectivity, and of course you can roll your own. I've found AFNetworking to be one of the best. It has a singleton class called AFNetworkReachabilityManager that abstracts some of the complexities for you. Specifically you'll want to look at the two boolean properties:
reachableViaWWAN
reachableViaWiFi
There is also a reachability changed status block that you can set:
– setReachabilityStatusChangeBlock:
AFNetworking Github
AFNetworkReachabilityManager
I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.
In Objective-C, this is how I checked to see if that function exists:
if (UIAccessibilityIsReduceMotionEnabled != NULL) { }
In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()
It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.
What is the proper way to check if these types of functions exist?
A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.
var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:
let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
println("Not available!")
} else {
println("Available!")
}
The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.
As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.
You could check to see if you're running in iOS 8 or higher --
var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8
The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:
// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
return (UIAccessibilityIsReduceMotionEnabled != NULL);
}
// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):
Swift code can use the availability of APIs as a condition at
run-time. Availability checks can be used in place of a condition in a
control flow statement, such as an if, guard, or while
statement.
Taking the previous example, you can check availability in an if
statement to call requestWhenInUseAuthorization() only if the method
is available at runtime:
let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Alternatively, you can check availability in a guard statement,
which exits out of scope unless the current target satisfies the
specified requirements. This approach simplifies the logic of handling
different platform capabilities.
let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Each platform argument consists of one of platform names listed below,
followed by corresponding version number. The last argument is an
asterisk (*), which is used to handle potential future platforms.
Platform Names:
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension
I'm currently making a 2-player strategy board game in Swift and need to connect two iPads over local WiFi or Bluetooth. No matter what I've tried today, I can't get them to detect each other (I've tried over local WiFi and Bluetooth).
Here is my authorization code which runs in the UIViewController when my app first launches (which always returns "Self local player is authenticated." along with the ID:
private func authenticateLocalPlayer() {
var localPlayer = getLocalPlayer()
// If Apple were doing their job right, this is what the proper code should look like:
// var localPlayer = GKLocalPlayer.localPlayer()
if ( !localPlayer.authenticated ) {
localPlayer.authenticateHandler = { (viewController : UIViewController!, error : NSError!) -> Void in
NSLog("Error: \(error)")
if viewController != nil {
// Authenticated?
self.presentViewController(viewController, animated: true, completion: nil)
NSLog("viewController is not nil")
} else if (localPlayer.authenticated == true) {
NSLog("Self local player is authenticated.")
NSLog("My name is \(localPlayer.playerID)")
} else {
NSLog("Not authenticated")
NSLog("Player is \(localPlayer.playerID)")
}
}
} else {
NSLog("Player is already authenticated!")
}
}
and here is my code to detect nearby devices in a separate UIViewController:
override func viewDidLoad() {
devicesLabel.text = "Waiting for devices..."
searchForDevices()
NSLog("Ran searchForDevices()")
}
private func searchForDevices() {
GKMatchmaker.sharedMatchmaker().startBrowsingForNearbyPlayersWithHandler() {
var status = $1 ? "true" : "false"
self.devicesLabel.text = "Reachability changed for player \($0) with status: \(status)"
}
}
No matter what I do with my two iPads (both are model iPad 3), neither one ever sees the other. Am I calling startBrowsingForNearbyPlayersWithHandler correctly?
Also notice that in the authorization code above, I'm using the Objective-C workaround recommended by this post: Game Center not authenticating using Swift, since the "Swift way" of doing that didn't work for me either.
I also ran Spelltower across both devices over local WiFi, so it looks like the hardware is functioning properly. Any idea what could be going wrong here?
I decided to abandon developing this through Game Center and to use Multipeer Connectivity instead.
You are not registering a class to receive invitation updates. You need to register a class and implement methods conforming to the protocol for GKLocalPlayerListener. See my response in this post (it is in Objective-C, but the same concept applies):
Some startBrowsingForNearbyPlayersWithReachableHandler questions
I have an iOS app that makes some small network requests on app launch (resource updates, etc). If the user turns off cellular access for the app in iOS Settings, they get a prompt from iOS about network usage every time they launch. Is there a way to know programmatically that cellular data for this app has been disabled, so that I can disable the requests at startup?
So I found this on the apple dev forums from an Apple engineer (https://devforums.apple.com/message/1059332#1059332).
Another developer wrote in to DTS and thus I had a chance to
investigate this in depth. Alas, the news is much as I expected:
there is no supported way to detect that your app is in this state.
Nor is there a way to make a "no user interaction" network connection,
that is, request that the connection fail rather than present UI like
this. If these limitations are causing problems for your app, I
encourage you to file a bug describing your specific requirements.
https://developer.apple.com/bug-reporting/
So it looks like it is not possible to detect if cellular data for your app has been turned off.
Edit
I filed a radar for this requesting that it be added. I just got this notification in my radar
We believe this issue has been addressed in the latest iOS 9 beta.
I looked through the API diffs, but so far I can't find the new API.
As of iOS9, the capability to check the setting to enable/disable use of cellular data for your app (Settings/Cellular/AppName) is available using Apple's CTCellularData class. The following code will set cellularDataRestrictedState when it is run initially and then set it and log whenever it changes:
import CoreTelephony
var cellularDataRestrictedState = CTCellularDataRestrictedState.restrictedStateUnknown
let cellState = CTCellularData.init()
cellState.cellularDataRestrictionDidUpdateNotifier = { (dataRestrictedState) in
if cellularDataRestrictedState != .restrictedStateUnknown { // State has changed - log to console
print("cellularDataRestrictedState: " + "\(dataRestrictedState == .restrictedStateUnknown ? "unknown" : dataRestrictedState == .restricted ? "restricted" : "not restricted")")
}
cellularDataRestrictedState = dataRestrictedState
}
Unfortunately (as of iOS11) this seems to check only the state of the app's switch - if your app's switch is set to enabled and the user switches the Cellular Data master switch to disabled, this API will return the app's state as being "not restricted".
Just wanted to add an Objective C version of the above Swift code for future travellers.
- (void)monitorCanUseCellularData {
if (GCIsiOS9) {
CTCellularData *cellularData = [[CTCellularData alloc] init];
NSLog(#"%ld", cellularData.restrictedState);
// 0, kCTCellularDataRestrictedStateUnknown
[cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
NSLog(#"%ld", state);
self.canUseCellularData = cellularData.restrictedState ==2?true:false;
}];
}
}
I have found that the CTCellularData class needs some time to get to the correct value. In my implementation I call the didUpdateNotifier very early after appDidFinishLaunching. By the time my networking call are returning with errors I definitely have a correct value for the restricted state.
class CellularRestriction: NSObject {
private static var cellularData = CTCellularData()
private static var currentState = CTCellularDataRestrictedState.restrictedStateUnknown
static var isRestricted: Bool {
currentState = cellularData.restrictedState
return currentState == .restricted
}
static func prepare() {
if currentState == .restrictedStateUnknown {
cellularData.cellularDataRestrictionDidUpdateNotifier = { state in
currentState = cellularData.restrictedState // This value may be inconsistent, however the next read of isRestricted should be correct.
}
}
}
}
You can detect if cellular data disabled using NWPathMonitor class. (https://developer.apple.com/documentation/network/nwpathmonitor)
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
cellMonitor.pathUpdateHandler = { path in
self.isCellConnected = path.status == .satisfied
}
Adding to dirkgroten's answer, you can use the Apple Reachability class, found here:
https://developer.apple.com/Library/ios/samplecode/Reachability/Introduction/Intro.html
It uses SCNetworkReachability, and is very straight forward to use, it will detect connectivity via Cell and WiFi as you will need to check both at start up.
There are lots of frameworks out there that will give you the status of your network connectivity, and of course you can roll your own. I've found AFNetworking to be one of the best. It has a singleton class called AFNetworkReachabilityManager that abstracts some of the complexities for you. Specifically you'll want to look at the two boolean properties:
reachableViaWWAN
reachableViaWiFi
There is also a reachability changed status block that you can set:
– setReachabilityStatusChangeBlock:
AFNetworking Github
AFNetworkReachabilityManager