How do I initialize a peripheral manager in Swift? - ios

I want to check if Bluetooth is enabled on my device and print the status to the console. My class conforms to CBPeripheralManagerDelegate and I have no errors. I read the documentation and I implemented the peripheralManagerDidUpdateState function but it never gets called. My understanding is that function is called when Bluetooth's availability changes. So if I turn Bluetooth off that function should be called and then should be called again once I turn Bluetooth back on. But that doesn't happen. I saw another question that had the same problem but the solution was written in Objective-C and didn't solve my problem.
When I run this code in viewDidLoad it returns unknown and I don't know why. I expected it to return powered on:
cbManager.delegate = self
if cbManager.state == .PoweredOn {
println("powered on")
} else if cbManager.state == .Unauthorized {
println("unauthorized")
} else if cbManager.state == .Unknown {
println("unknown")
} else if cbManager.state == .PoweredOff {
println("pow off")
} else if cbManager.state == .Unsupported {
println("unsupported")
}
I declare cbManager outside of viewDidLoad because if I don't the peripheralManagerDidUpdateState doesn't recognize the manager.
That other question's problem was "the object myPeripheralManager is deallocated as soon as viewDidLoad method returns". I think that's my problem too but I don't know how to fix it.
Here's my manager declaration:
let cbManager = CBPeripheralManager() which is outside of viewDidLoad.
Thanks!
UPDATE:
After many hours I think I have found the problem. I don't know if this is why it didn't work but it works now. I was using a CBPeripheralManager when I should've been using a CBCentralManager. This question helped show me (even though it was a completely different problem) the difference between a central and peripheral manager.
UPDATE #2:
Also, the manager that was being passed into peripheralManagerDidUpdateState was different than the manager I was using in the function itself. This in combination with using the wrong manager caused the problems. Hope this helps someone!

Related

How to detect if iOS devices is connected using wifi in Swift?

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
}

iOS 14 StoreKit - SKCloudServiceController's requestusertoken not being called properly

I am trying to play songs on iOS 14 using Apple Music API.
I have the developer's token, and I have asked for the permission for accessing the user's apple music.
However, when I call requestusertoken api, its closure never gets called, so obviously I don't receive anything from the request - not even an error. It's driving me crazy.
Here is my code. What am I doing wrong?
func getUserToken() -> String {
var userToken = String()
let lock = DispatchSemaphore(value: 0)
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
guard error == nil else { return }
if let token = receivedToken {
userToken = token
lock.signal()
}
}
lock.wait()
return userToken }
I've tried the code and there were two major problems.
First, DispatchSemaphore makes the return line execute too early. Second, original developer token doesn't work due to latest iOS 14.3 issue.
So, I first erased DispatchSemaphore.
func getUserToken() {
var userToken = String()
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
guard error == nil else { return }
if let token = receivedToken {
userToken = token
print(userToken)
}
}
}
Then tweaked developer token following this repository.
Now, it's printing user token properly. I hope this helped.
I think we've all got stuck on the same tutorial. To fix, I put it on a different thread as the lock was holding up the main thread hence preventing the completion handler.
DispatchQueue.global(qos: .background).async {
print(AppleMusicAPI().fetchStorefrontID())
}
If it's the same tutorial, this will put the fetchStorefrontID() and the getUserToken() methods (which is called by the former) on a background thread and allow the completion handlers and the lock.signal() to occur.
If it's not, then this shall suffice for an answer:
DispatchQueue.global(qos: .background).async {
getUserToken()
}
Did you remove Bearer from your developerToken?
Okay so I know what's going on -- the DispatchSemaphore is being locked right after it's being created -- so it's never executing that code. Once I made the Semaphore value 1 instead of 0 it started to execute -- but then I had issues with the rest of the code because of the sequencing of events.
It looks like perhaps you're working with the same tutorial I was working through on Apple Music SDK integration -- if that's the case, I basically tweaked the code to :
download the user token to a local variable, and then the other methods begin to reference it in their requests.
Remove the Semaphore lock in the getuserToken() method only
The rest started working again, without having to change any other DispatchSemaphore values.
By no means am I an expert in how DispatchSemaphore works and best practices with apple music user tokens, but wanted to at least let you know why you were running into the same wall as me -- with no code being executed at all.

Why doesn't my iOS (Swift) app properly recognize some external display devices?

So I have an odd issue and my google-fu utterly fails to even provide me the basis of where to start investigating, so even useful keywords to search on may be of use.
I have an iOS application written in swift. I have a model hooked up to receive notifications about external displays. On some adaptors, I'm able to properly detect and respond to the presence of an external display and programatically switch it out to be something other than a mirror (see code block below). But with another adaptor, instead of just 'magically' becoming a second screen, I'm asked to 'trust' the external device, and it simply mirrors the device screen. Not the intended design at all.
func addSecondScreen(screen: UIScreen){
self.externalWindow = UIWindow.init(frame: screen.bounds)
self.externalWindow!.screen = screen
self.externalWindow!.rootViewController = self.externalVC
self.externalWindow!.isHidden = false;
}
#objc func handleScreenDidConnectNotification( _ notification: NSNotification){
let newScreen = notification.object as! UIScreen
if(self.externalWindow == nil){
addSecondScreen(screen: newScreen)
}
}
#objc func handleScreenDidDisconnectNotification( _ notification: NSNotification){
if let externalWindow = self.externalWindow{
externalWindow.isHidden = true
self.externalWindow = nil
}
}
The worst issue here is that because I'm connecting to an external display to do this, I can't even run this code through the debugger to find out what is going on. I don't know where to even begin.
Any ideas?
Edit:
Thanks to someone pointing out wifi debugging, I can tell you my notifications are firing off, but they're both firing at the same time, one after the other, when the external adaptor is disconnected.

Knowing programmatically if cell data is disabled for the app for iOS [duplicate]

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

How do I know if cellular access for my iOS app is disabled?

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

Resources