I'm trying to broadcast from an iOS 8.1+ device using Swift. When I run the app, it does successfully broadcast ... but only for a second. I know this because, from another device with a 'scanner' app, I see the beacon appear; the print statement for "power on" also appears as expected.
I have other print statements in peripheralManagerDidUpdateState, but they're never called, so I have no clue why broadcast stops so quickly.
I'm NOT trying to do anything fancy (monitor for regions, determine proximity, broadcast in the background, etc.) -- this is just a normal, run-of-the-mill iBeacon transmit from the foreground.
import CoreBluetooth
class ViewController: UIViewController, CBPeripheralManagerDelegate {
var beaconRegion = CLBeaconRegion()
var beaconData = NSDictionary()
var beaconManager = CBPeripheralManager()
...
Later:
self.beaconRegion = CLBeaconRegion(proximityUUID: bleUuid,
major: bleMajor,
minor: bleMinor,
identifier: "com.please.work")
Later, to initiate broadcast:
self.beaconData = self.beaconRegion.peripheralDataWithMeasuredPower(nil)
self.beaconManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
The delegate:
func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager!) {
if(peripheral.state == CBPeripheralManagerState.PoweredOn) {
println("powered on")
self.beaconManager.startAdvertising(self.beaconData)
} else if(peripheral.state == CBPeripheralManagerState.PoweredOff) {
println("powered off")
self.beaconManager.stopAdvertising()
}
else {
println("something else changed")
}
}
UPDATE
This might be due to a problem with my device (iPhone 6); here are my observations:
Rebooting the device clears the issue.
I'm seeing phenomenon in normal apps. First, I start emitting with this: iBeacon Emitter app. Then (on another device), I register the UUID and monitor with this iBeacon Scanner app. The device appears, but after ~minute, it disappears. Thereafter, if I toggle the emitting device, I see the rapid on/off behavior I'm troubleshooting.
More concerning, the behavior occurs across apps. If I reboot (and clear the issue, see #1), and then cause the issue (see #2) ... the problem then appears via other emitter/scanner apps.
I diagnosed this problem as some sort of low-level Bluetooth conflict with the "Knock to Unlock" app.
"Knock to Unlock" uses BLE for 2-way comms with a computer. The moment I uninstalled the app, the problem resolved itself. I hope this saves someone in the same case a little aggravation.
A few thoughts:
Are you sure your CBPeripheralManager instance is not going out of scope or being overwritten and garbage collected, that your ViewController remains in the foreground and the screen is not locked?
Try using another transmitter app like the free Locate app to verify it is not a device specific issue.
If the Locate app works on the same device, then there must be something else in your app that is interfering with the broadcast at a later time. Perhaps you can share code on Github or elsewhere so others can test.
Related
I have an app demanding to scan BLE devices around in background mode when app is not active in foreground.
I have implemented such functionality using CoreBluetooth framework. This is code, I am using to scan device. First I have all device in DB, fetch and creating array.
for item in self.allItems ?? [] {
let uuid = UUID(uuidString: item.identifier)!
let id = CBUUID(nsuuid: uuid)
self.allServiceIds.append(id)
}
And when start scanning, passing same array in method.
self.centralManager?.scanForPeripherals(withServices: self.allServiceIds, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])
Also I have tried to pass service ids in array as I read lots of articles suggesting in background mode it is required.
Also inside Capabilities, I have checked required options. But still it is not scanning in when app is in background.
Any ideas are welcome.
A few tips:
Get your app working in the foreground first. Only once this is working should you try to get it working in the background.
Make sure that bluetooth is turned on in phone settings.
Make sure you have obtained Bluetooth permission from the user. An iPhone will send a dialog to prompt you for this permission the first time an app runs (it is triggered by CBCentralManager usage.) If you deny this permission, you won't see the dialog again and you must go to Settings -> Your App -> Permissions -> Bluetooth to enable it manually.
Take care that you have set the CBCentralManagerDelegate properly and that you are getting callbacks. In particular, log the callback to centralManagerDidUpdateState(_ central: CBCentralManager) and make sure that you see central.state transition to .poweredOn. Only once you reach the poweredOn state should you start scanning. See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518888-centralmanagerdidupdatestate
Start off testing in the foreground without filtering self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true]) (note the withServices: nil). Only once you get callbacks detecting peripherals should you start filtering for specific service UUIDs. This will help you eliminate all other problems before filtering on the service.
If you can get all of the above working, there are tricks you can do to get duplicate detections even in the background.
If you cannot get the above working, please described the results of the above, and show more of your code including where you instantiate the CBCentralManager, where you set the delegate, showing the delegate callback methods, and describing which delegate callbacks get called and when.
Situation:
Since our users have updated their iOS to 11 and/or WatchOS to 4, our iOS app doesn't seem to fire any scheduled timers when the app gets started by our WatchOS app. Maybe we are doing something wrong when starting our main app from the WatchOS app.
Context & code:
Our WatchOS app is a companion app that lets the user start/stop our iPhone app in the background by pressing a button. We do this by using:
func startMainApp() {
guard WCSession.default().isReachable == true else {
print("Watch is not reachable")
return
}
var data = [String : AnyObject]()
data[WatchActions.actionKey()] = NSNumber.init(value: WatchActions.startApp.rawValue as Int)
WCSession.default().sendMessage(data, replyHandler: { (result: [String : Any]) in
let resultNumber = result[WatchActions.resultKey()] as? NSNumber
let resultBool = resultNumber!.boolValue
if resultBool == true {
self.setModeActivated()
} else {
self.setModeActivationFailed()
}
}) { (error: Error) in
if (error as NSError).code != 7012 {
print("start app error: \(error.localizedDescription)")
self.setModeActivationFailed()
}
}
}
Then in our main app, we receive the message and start our base controller:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
if let actionNumber : NSNumber = message[WatchActions.actionKey()] as? NSNumber {
if let watchAction : WatchActions = WatchActions(rawValue: actionNumber.intValue) {
switch(watchAction) {
case .isAppActive:
let result = BaseController.sharedInstance.sleepAndWakeUpController.isAwake()
replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])
return
case .startApp:
AudioController.sharedInstance().playActivatedSound()
let isRunningOnForeground = ApplicationStateHelper.isActive()
if isRunningOnForeground == false {
BaseController.sharedInstance.start(inBackground: true)
}
let result = true
replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])
DDLogInfo("[APPLE WATCH] [didReceiveMessage] [.startApp]")
return
}
}
}
replyHandler([WatchActions.resultKey() : NSNumber.init(value: false as Bool)])
return
}
Everything seems to work as before, we correctly get GPS locations, all our processes get started, however, Timer objects that get started, don't fire.
This worked perfectly before on iOS 10, so I suspect this has something to do with iOS 11 background states that work differently. However, I cannot seem to find any documentation of this.
Extra info:
On iOS 10, when we started our main app this way, the app got visible in the multitask view on the iPhone. Now on iOS 11, it isn't visible in the multitask view, however it does run on background. I successfully see local notifications that I schedule on the background, I can debug through the active code and when tapping the app icon, the app is immediately available.
Our WatchOS app has the deployment target of 2.0
Debugged via XCode with device connected, using Debug-->Attach to PID or Name-->Entered app name. Then start our app from the Apple Watch and debug.
Reproducable on iOS 11.0.3 with WatchOS 4.0 on iPhone 6
Questions:
What is the best way to start our main app from the watch app? Has something changed in iOS 11/WatchOS 4 regarding to background states? Can I find documentation of this? Could this be an iOS bug?
All I can offer you is a confirmation that this behavior did in fact change from iOS 10 to iOS 11. It is my suspicion that the behavior on iOS 10 (and earlier?) was incorrect. Apple doesn't have any qualms about changing behavior that was inadvertent/what they deem incorrect even if developer's come to rely on the behavior (I'm pretty sure I used this behavior on my last watch project).
The fact is that the UIApplication's state when launched by a message from the watch is background. Timer's aren't supposed to run when the application is in the background unless using particular background execution modes/background task. That fact is pretty well known and is usually encountered quite early on in an iOS developer's career. The fact that timer's would run in the background when launched from the watch was, I can surmise, a mistake.
I do not know your use case, i.e. why you were relying on those timers, but one thing you can do that is quite simple is to create an empty background task that will get you a little more time when the app is launched.
var backgroundTask: UIBackgroundTaskIdentifier?
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "app Start Task", expirationHandler: {
guard let task = backgroundTask else { return }
UIApplication.shared.endBackgroundTask(task)
})
let timer = Timer(timeInterval: 1, repeats: true) { (timer) in
print("Running")
}
If you need a more consistent, longer running solution, you may need to leverage your location updates as an opportunity to do whatever work the timer is currently for. There are plenty of other background modes to pursue as well.
Summary of your questions:
Q: What is the best way to start our main app from the watch app?
A: Your proposed code is a great way to launch the companion app.
Q: Has something changed in iOS 11/WatchOS 4 regarding to background states?
A: No, especially in regards to timers. The different behavior is likely a correction.
Q: Can I find documentation of this?
A: I can't. Sometimes you can squeeze this information out of apple engineers on the forums or via the code level support questions through your developer account or go to WWDC.
Q: Could this be an iOS bug?
A: The earlier behavior was likely the bug.
When app is closed and not running in background then location never track in iOS 11 it's not an iWatchOS 4 and iOS 11 Bug.
Changes to location tracking in iOS 11
follow this documentation link: Changes to location tracking in iOS 11
iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking.
If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.
For example, let’s consider those two apps again; the continuous background location and the significant location change monitoring app. Suppose the user goes for a run with the continuous background location app. They’ll go on the run, they’ll come back, they’ll see the solid arrow the whole time, and when they look at their map, they’ll see every twist and turn they took. When they install that app that uses significant location change monitoring, they’ll see the same thing, a solid arrow. As far as the user is aware, this app is probably receiving the same amount of information as their run tracking app. So if users are misinterpreting our signals, we decided the best way to fix this was to adjust how we indicate location usage.
watchOS has access to many of the same technologies found in iOS apps; however, even if a technology is available, you may not be able to use it in quite the same way you did on iPhone.
Do not use background execution modes for a technology. In general, Watch apps are considered foreground apps; they run only while the user interacts with one of their interfaces. As a result, the corresponding WatchKit extension cannot take advantage of most background execution modes to perform tasks. might be help this documentation:
Leveraging iOS Technologies for watch
I made a ibeacon project, now I found two problems:
first,when mobile phone lock screen, I'm going to scan for Bluetooth devices (I'm sure in the beacon region), sometimes Scan failed and returned empty array
second, when I lock screen, sometimes didEnterRegion and didExitRegion have stopped, when I am the light of the screen, they went on again
Now I want to scan the device every time when I lock the screen,what should i do?
MonitoringForRegions code:
let region = BRTBeaconRegion.init(proximityUUID: proxiID, identifier: proxiID.UUIDString)
region.notifyOnEntry = true
region.notifyOnExit = true
region.notifyEntryStateOnDisplay = true
BRTBeaconSDK.startMonitoringForRegions(region)
Appdelegate delegate code:
func beaconManager(manager:BRTBeaconManager,didEnterRegion region:BRTBeaconRegion){
if region.notifyOnEntry {
//PublicMethod().sendLocalNotification(BEACON_TIP_IN)
print("\(NSDate())-------enter--------")
}
}
func beaconManager(manager:BRTBeaconManager,didExitRegion region:BRTBeaconRegion){
if region.notifyOnExit {
//PublicMethod().sendLocalNotification(BEACON_TIP_OUT)
print("\(NSDate())-------exit--------")
}
}
func beaconManager(manager:BRTBeaconManager,didDetermineState state:CLRegionState,forRegion region:BRTBeaconRegion){
print("didDetermineState")
}
scan code:
BRTBeaconSDK .startRangingBeaconsInRegions(regionArray) { (beacons, region, error ) in
for beacon in beacons as! [BRTBeacon]{
print("beacons count:\(beacons.count) name :\(beacon.name) macaddress:\(beacon.macAddress) major:\(beacon.major) minor:\(beacon.minor) ")
}
}
updated at 14:25
I found the first problem more accurately described, when the lock screen, the device is close to the phone, can be scanned, but not far away (this distance can be scanned at the front desk)
So I guess if the scanning distance is not accurate when lock screen?
Did you turn on the Background Modes for Bluetooth in the Xcode project's capabilities page?
https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html
You need to first understand the mechanism of CoreBluetooth. Especially you need to understand the different between Ranging and Monitoring, and the limitations of them in background mode. Some operations can only be achieved in the foreground mode.
Detecting beacons via iBeacon Monitoring & Ranging vs CoreBluetooth scanForPeripheralsWithServices
didEnterRegion and didExitRegion is not realtime, there is buffer time in between exit and reenter the region. Also, if you are already in the region before you call the monitor function, the didEnterRegion won't be trigged.
Update
1、Not open ,Before trying to open, or have the same problem
You need to turn it on, and do some settings in order to implement background scanning.
2、Thank you very much, but I found the scan failed, not every time, occasionally.That's what I don't understand.
3、yes,My experiments are the first to leave the area, then enter the area, because I have two beacon equipment together, UUID is different, close one, then I went to scan, and then found sometimes does not scan, sometimes it is possible
I cant find the code of the Bluetooth library that you are using. It seems like it is written by Mainland China developer? Can you post the link of that library?
My exp, the CoreBluetooth ranging functions are quite reliable. So I guess the problem is you didnt turn on the background mode. Turn on Acts as a Bluetooth LE accessory and Uses Bluetooth LE accessories in the Capabilities tab.
Also, I suggest you to read / bookmark Radius Networks's blog. Their developer blog is worth reading.
http://developer.radiusnetworks.com/2014/11/13/extending-background-ranging-on-ios.html
Our app is using WLAN to communicate with a wireless device. When our app is installed in iOS 10. Sometimes, udp socket doesn't work. The reason for that is, in iOS 10 they added a new setting or permission under your app that allows the user to switch on or off the user of WLAN or cellular data.
The following would appear in the settings of the app:
When I tap on the Wireless... It will bring me to this UI:
After allowing WLAN use. The app would work fine.
Now, the problem is, sometimes, or in some devices running iOS 10, the settings that I just showed you doesn't appear(I am referring to the setting shown on the first image). So, is there anything I can do to make that settings always appear? It seems that sometimes iOS system doesn't recognize that my app is using wireless data. And it would result in my app would never get to use WLAN forever.
There is no user permission to use WIFI in iOS10.
The application in your screenshot (BSW SMART KIT) is using Wireless Accessories (WAC), i.e. is able to connect to wireless speakers. To accomplish this the Wireless Accessory Configuration capability is required. This is what you can dis/enable in the systems settings (your screenshot).
The switch in the settings shows up after connecting to a device via WIFI through WAC. You can see this behaviour in your sample app (BSW SMART KIT) too.
This sample code might let you get the idea. Detailed information in Apples documentation.
After a time of researching. I ended up seeking help with Apple Code Level Support. Apple states that this problem would most probably occur when you reskin your app. They say that probably it's because of the Image UUID of the main app and the reskinned app are the same. When you install both of them in your phone, the system will treat the reskinned app as the same app compared to the main app. So, if the one app fails to access WLAN, then it will also affect the other one. According to them, this appears to be a bug in iOS. And currently, they don't have any solution for the developers. This is the radar bug number:
What I did to somehow lessen the occurrence of the problem is to add tracking to the Network Restriction by using the following code.
- (void)startCheckingNetworkRestriction
{
__weak AppDelegate *weakSelf = self;
_monitor = [[CTCellularData alloc] init];
_monitor.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState state)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
NSString * statusStr;
switch(state)
{
case kCTCellularDataRestrictedStateUnknown:
{
statusStr = #"restriction status:Unknown";
}
break;
case kCTCellularDataRestricted:
{
statusStr = #"restriction status:restricted";
[weakSelf performUrlSession];
}
break;
case kCTCellularDataNotRestricted:
{
statusStr = #"restriction status:not restricted";
}
break;
default:
{
abort();
}
break;
}
NSLog(#"Restriction state: %#", statusStr);
}];
};
}
Take note that you have to import CoreTelephony to do this.
#import CoreTelephony;
when I detect that the network is restricted. I will open a URL session to force internet access attempt and would hope that the restriction alert dialog would pop out. Once the alert is pop out, then the WLAN Access Settings that I was talking about would definitely appear under the settings. There are times that this doesn't work. If it happens, then you'll just have to rename the bundle ID, and make a couple of changes to your code and then rebuild it a couple of times (Well, that's what I did when I was experimenting this). Reinstalling the app won't do a thing. Restarting and resetting the phone won't do either.
Hope this helps.
I want to develop an app that detecting the user's moving way (walking, cycling, driving etc...) and send a specific UILocalNotification for each activity type.
My question is: is it possible to detect it on the background (when the app is completely closed) without draining the device's battery? What will be the best way to do it?
Thank you!
There is coprocessor m7(+) in iPhones upper 5s.
It gives you possibility to get device motion.
Just
import CoreMotion
in your file.
Create a CMMotionActivityManager object:
let motionActivityManager = CMMotionActivityManager()
Check if it`s available on your device:
motionActivityManager.isActivityAvailable()
Use this method:
motionActivityManager.startActivityUpdates(to: OperationQueue.main) { (activity) in
if (activity?.automotive)! {
print("User using car")
}
if (activity?.cycling)! {
print("User is cycling")
}
if (activity?.running)! {
print("User is running")
}
if (activity?.walking)! {
print("User is walking")
}
if (activity?.stationary)! {
print("User is standing")
}
if (activity?.unknown)! {
print("Unknown activity")
}
}
It would return you types of user activity.
Regarding the user activity which can be handled in background tasks are the below once which does not mention about (walking, cycling,driving etc...)
Implementing Long-Running Background Tasks
For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background,
such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as
a navigation app Apps that support Voice over Internet Protocol
(VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Yes it´s possible to do that!
If your iOS app must keep monitoring location even while it’s in the
background, use the standard location service and specify the location
value of the UIBackgroundModes key to continue running in the
background and receiving location updates. (In this situation, you
should also make sure the location manager’s
pausesLocationUpdatesAutomatically property is set to YES to help
conserve power.) Examples of apps that might need this type of
location updating are fitness or turn-by-turn navigation apps.
Read more here.