I'm testing out my code that starts an HKWorkoutSession collects samples and ends then saves the workout using the old HKWorkoutConfiguration API (not the new HKWorkoutBuilder API)....my understanding is that the previous API should still work.
However when testing in the watch simulator, starting and stopping a workout using Xcode 10 Beta 4 and 5, I noticed that I am able to start a workout and query samples, however when I call healthStore.stop there is a 5 or 6 second delay until the workout ends?
// Create workout configuration
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .running
workoutConfiguration.locationType = .outdoor
do {
workoutSession = try HKWorkoutSession(configuration: workoutConfiguration)
workoutSession?.delegate = self
} catch {
fatalError(error.localizedDescription)
}
func start(_ workoutSession: HKWorkoutSession) {
healthStore.start(workoutSession)
}
func end(_ workoutSession: HKWorkoutSession) {
healthStore.end(workoutSession)
}
Update 9/20/18: I filed a radar, it was marked as dup....as of Xcode 10 GM release this bug is still present. Confirmed this does NOT happen on device, only simulator.
Related
This question already has answers here:
update label from background timer
(1 answer)
iOS Swift Timer in not firing if the App is in the background
(2 answers)
Closed 3 years ago.
I'd like to create a simple timer app that works the same way of the native timer of iOS.
To start I just write some simple code that print the second starting to 0 to infinite.
The first problem was that if you go to the home screen, the task obviously stops to work
so I easily sorted just checking the box into background mode - Audio Airplay, and Picture in Picture (inside project - targets - Signing and Capabilities)
now my task works fine.. even in the background.. unless you put your app into a real device
in this case when you go into the background it doesn't work
after that I searched online for a solution and what I've learnt that Apple does't allow the apps to work into the background as you pleased and after 180 seconds the system just "kill" the background task. I just wonder how all the timer app in the Appstore works..
An Interesting thing that I've come across was when I watched an Apple developer conference that they talk about this new framework of background that you basically can make your app working in the background for heavy tasks when the iPhone is charging, and not only that you can forecast when the user will use your app and have some background tasks that work in the background in order to prepare the app to be updated. The link is this https://developer.apple.com/videos/play/wwdc2019/707/
after this I've tried different approaches to sort my problem but nothing has worked yet.. I have followed this tutorial which I found interesting https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9 but it didn't work for me (maybe because of the version of swift outdated or simply because of me) if you guys have managed to make the timer work in the background in your real device let me know.. I would like to understand it well rather than copy and paste the code.
Happy coding to all
the tutorial code:
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
/*
If the timer is suspended, calling cancel without resuming
triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
*/
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}
THE PROBLEM:
I want to run a simple function, 5 seconds after app goes into background.
I had to implement BGTaskScheduler, to support iOS 13.
The old implementation for BackgroundTask works for me on older iOS versions.
I added background modes as requested (BLE accessories is ticked because we perform a small BLE operation in this function):
Then, I prepared the Info.plist according to the docs (Identifier is fake just for StackOverflow question):
Before didFinishLaunchingWithOptions is ended, I register my BackgroundTask:
if #available(iOS 13.0, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.MyID", using: .global()) { (task) in
print("My backgroundTask is executed NOW!")
task.expirationHandler = {
task.setTaskCompleted(success: true)
}
}
}
Now, when the app run the didEnterBackground method, I submit a BackgroundTaskRequest:
if #available(iOS 13.0, *) {
do {
let request = BGAppRefreshTaskRequest(identifier: "com.example.MyID")
request.earliestBeginDate = Calendar.current.date(byAdding: .second, value: 5, to: Date())
try BGTaskScheduler.shared.submit(request)
print("Submitted task request")
} catch {
print("Failed to submit BGTask")
}
}
The problem here is that it is VERY inconsistent. Apple guarantee that the task will not be executed before the given date, but does not guarantee that it will be executed on the exact time (I'm fine with a small delay).
However, when I ran the app, it did not work 100% of the times, regardless if I provided the task request a delay (using earliestBeginDate) so it used to go first try 7 seconds (instead of 5), next time I submitted the task it took 26 seconds, third time never arrived the closure.
Am I implementing the BackgroundTask the wrong way? I've searched all over the internet some answer but did not encounter anyone having this problem.
As badhanganesh said, It seems like the task will be executed only when the system decides to do so.
Apple said that during WWWDC 2019, session #707.
I've been reading the heart rate from the Apple Watch (Series 2) but, after updating to OS4.2.2 yesterday (and updating the iPhone 7 to the latest OS, iOS 11.2.5), calling createHeartRateStreamingQuery() from the HealthKitManager stopped working on my device.
The code wasn't changed and things still work on the simulator. For reference, here's the code (the invocation that is working in the simulator but seems to not work on the device after the update is in the if-let statement within the .running case):
func workoutSession(_ workoutSession : HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
switch toState {
case .running:
guard let workoutStartDate = workoutStartDate else {
print(workoutStartDate)
return
}
if let query = healthKitManager.createHeartRateStreamingQuery(workoutStartDate) {
self.heartRateQuery = query
self.healthKitManager.heartRateDelegate = self
healthKitManager.healthStore.execute(query)
}
case .ended:
print("Workout ended")
if let query = self.heartRateQuery {
healthKitManager.healthStore.stop(query)
}
default:
print("Other workout state")
}
}
Does anyone have any ideas? Or, has anyone else had a problem with the Apple Watch/Healthkit since the update? Thanks!
Things are working now and I'm guessing it has to do with when I set the workout date. I had set the workout date before I created the workout session. Now I'm doing it just after the workout session is created. That's the only substantial change I made fumbling around for possible solutions.
The following tests works fine on iOS 11. It dismisses the alert asking permissions to use the locations services and then zooms in in the map. On iOS 10 or 9, it does none of this and the test still succeeds
func testExample() {
let app = XCUIApplication()
var handled = false
var appeared = false
let token = addUIInterruptionMonitor(withDescription: "Location") { (alert) -> Bool in
appeared = true
let allow = alert.buttons["Allow"]
if allow.exists {
allow.tap()
handled = true
return true
}
return false
}
// Interruption won't happen without some kind of action.
app.tap()
removeUIInterruptionMonitor(token)
XCTAssertTrue(appeared && handled)
}
Does anyone have an idea why and/or a workaround?
Here's a project where you can reproduce the issue: https://github.com/TitouanVanBelle/Map
Update
Xcode 9.3 Beta's Changelogs show the following
XCTest UI interruption monitors now work correctly on devices and simulators running iOS 10. (33278282)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.waitForExistence(timeout: 10) {
allowBtn.tap()
}
Update .exists to .waitForExistence(timeout: 10), detail please check comments.
I had this problem and River2202's solution worked for me.
Note that this is not a fix to get the UIInterruptionMonitor to work, but a different way of dismissing the alert. You may as well remove the addUIInterruptionMonitor setup. You'll need to have the springboard.buttons["Allow"].exists test anywhere the permission alert could appear. If possible, force it to appear at an early stage of the testing so you don't need to worry about it again later.
Happily the springboard.buttons["Allow"].exists code still works in iOS 11, so you can have a single code path and not have to do one thing for iOS 10 and another for iOS 11.
Incidentally, I logged the base issue (that addUIInterruptionMonitor is not working pre-iOS 11) as a bug with Apple. It has been closed as a duplicate now, so I guess they acknowledge that it is a bug.
I used the #River2202 solution and it works better than the interruption one.
If you decide to use that, I strongly suggest that you use a waiter function. I created this one in order to wait on any kind of XCUIElement to appear:
Try it!
// function to wait for an ui element to appear on screen, with a default wait time of 20 seconds
// XCTWaiter was introduced after Xcode 8.3, which is handling better the timewait, it's not failing the test. It uses an enum which returns: 'Waiters can be used with or without a delegate to respond to events such as completion, timeout, or invalid expectation fulfilment.'
#discardableResult
func uiElementExists(for element: XCUIElement, timeout: TimeInterval = 20) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == true"), object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
guard result == .completed else {
return false
}
return true
}
I have a fairly simple app that parses a RSS feed and shows it's content in a table view. It's available on the App Store. I have Crashlytics crash reporting integrated. I recently received two reports. These are a little difficult to decipher.
This has occurred in an iPhone 6 running iOS 10.2.1.
This is from an iPhone 5 running iOS 10.2.1.
Even though it says it's crashing due to privacy violations, I'm not accessing any services that requires permission in my app.
Also searching on com.apple.root.default-qos lead me to believe that this may have something to do with background threads. The only place where I use a background thread is to parse the RSS feed data.
DispatchQueue.global(qos: .background).async {
guard let data = try? Data(contentsOf: URL) else {
return
}
do {
let xmlDoc = try AEXMLDocument(xml: data)
if let items = xmlDoc.root["channel"]["item"].all {
self.posts.removeAll()
for item in items {
let title = item["title"].value ?? ""
// ...
self.posts.append(jobPost)
}
DispatchQueue.main.async {
self.saveposts(self.posts)
self.posts.sort { $0.publishDate > $1.publishDate }
self.tableView.reloadData()
UIApplication.shared.toggleNetworkActivityIndicator(show: false)
self.toggleUI(enable: true)
if self.refreshControl.isRefreshing { self.refreshControl.endRefreshing() }
}
}
} catch let error as NSError {
print("RSS parsing failed: \(error)")
self.showErrorAlert(error)
UIApplication.shared.toggleNetworkActivityIndicator(show: false)
self.toggleUI(enable: true)
if self.refreshControl.isRefreshing { self.refreshControl.endRefreshing() }
}
}
I tested this code on my iPhone 5 running iOS 9.3.5 and simulators running iOS 10.2 but no crash occurred.
Is there any other way to track down this problem?
I would double check all your permissions. In my case, starting with iOS10 you need permissions to save stuff to the user's camera roll. In my app, I was showing a default share sheet and whenever a user selected "save photo" the app crashed with one of these very not helpful error messages. I added
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow you to save charts and graphs from the app to your phone.</string>
to my info.plist, clean & run. And everything the problem was solved.