How to update Apple Watch complications automatically? - watchkit

Currently, my code doesn't work. I still have to manually refresh it.
I want the complication to automatically update every 12 hours.
func getTimelineStartDate(for complication: CLKComplication, withHandler handler: #escaping (Date?) -> Void) {
let date = Calendar.current.startOfDay(for: Date())
print("timeline start date :\(date)")
handler(date)
}
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: #escaping (Date?) -> Void) {
var date = Calendar.current.startOfDay(for: Date())
date = Calendar.current.date(byAdding: .day, value: 2, to: date)!
print("timeline end date:\(date)")
handler(date)
}
func getNextRequestedUpdateDate(handler: #escaping (Date?) -> Void){
handler(Date(timeIntervalSinceNow: 60*60*12))
}

It seems the data source methods are not implemented. It's needed to be implemented for the refresh.
At the start of a scheduled update, ClockKit calls either the
requestedUpdateDidBegin or requestedUpdateBudgetExhausted method,
depending on the state of your complication’s time budget. You must
implement one or both of those methods if you want to add data to your
timeline. Your implementation of those methods should extend or reload
the timeline of your complication as needed. When you do that,
ClockKit requests the new timeline entries from your data source. If
you do not extend or reload your timeline, ClockKit does not ask for
any new timeline entries.
func requestedUpdateDidBegin() {
let server=CLKComplicationServer.sharedInstance()
for complication in server.activeComplications {
server.reloadTimelineForComplication(complication)
}
}
For more information check this.

You can use the functions below to populate your complication with data.
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: #escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries prior to the given date
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: #escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries after to the given date
handler(nil)
}
See the App Programming Guide for watchOS

Related

SwiftUI removing Firebase remove observer error

I have the following code:
Here is where I init the database ref and the handle
private let gameResult = Database.database().reference().child("GameResults")
//
private var gameStatusRefHandle: DatabaseHandle!
This is the function where I set the handle
func getPlayerStatus(gameId: String, completion: #escaping (_ success: Bool) -> Void) {
self.gameStatusRefHandle = self.gameResult.child(gameId).child((Auth.auth().currentUser?.uid)!).observe(DataEventType.value) { (snapshot) in
completion(snapshot.value as! Bool)
}
}
and this is where I remove it:
func removeObserverHandle(gameId: String) {
self.gameResult.child(gameId).child((Auth.auth().currentUser?.uid)!).removeObserver(withHandle: self.gameStatusRefHandle)
}
The above code is all in a class:
class GameStore: ObservableObject {
private let gameResult = Database.database().reference().child("GameResults")
//
private var gameStatusRefHandle: DatabaseHandle!
func getPlayerStatus(gameId: String, completion: #escaping (_ success: Bool) -> Void) {
self.gameStatusRefHandle = self.gameResult.child(gameId).child((Auth.auth().currentUser?.uid)!).observe(DataEventType.value) { (snapshot) in
completion(snapshot.value as! Bool)
}
}
func removeObserverHandle(gameId: String) {
self.gameResult.child(gameId).child((Auth.auth().currentUser?.uid)!).removeObserver(withHandle: self.gameStatusRefHandle)
}
}
the setting of the handle and the removal are done in the SwiftUI view on the OnAppear and onDisappear
The above works when user navigates to the view and the onAppear function fires. When the user goes back and the onDisappear function fires I get the following error:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value 2021-05-20 21:03:26.480564-0400 [33352:1962213] GameStore.swift:316: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Not sure why that is since as you can see I am setting the handle in the getPlayerStatus function

perform network call and proceed - asynchronous task

i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}

Reload/refresh a scene after receive remote notification swiftUI

I have this problem. I'm receiving a notification from CloudKit using application:didReceiveRemoteNotification in AppDelegate. I'm able to receive the recordId, fetch it, and save it successfully. The problem is, the scene doesn't refresh.
final class UserData: ObservableObject {
#Published var items: [Item] = []
func saveFetchedItem(recordId: CKRecord.ID, reason: CKQueryNotification.Reason) {
fetchAndSaveRemoteDataFromCloudKit(recordId: recordId, reason: reason, userData: self)
}
}
in AppDelegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let notification: CKNotification = CKNotification(fromRemoteNotificationDictionary: userInfo)!
if notification.notificationType == .query {
let queryNotification = notification as! CKQueryNotification
let recordId = queryNotification.recordID
let reason: CKQueryNotification.Reason = queryNotification.queryNotificationReason
/// reasons:
/// .recordCreated, .recordUpdated, .recordDeleted
UserData().saveFetchedItem(recordId: recordId!, reason: reason)
}
completionHandler(.newData)
}
In fetchAndSaveRemoteDataFromCloudKit, I'm passing the UserData in order to save the item received and it works. The problem is, I show the items on a List and the list doesn't refresh, even when I print the userData.items and the new item is there.
Is there a connection between AppDelegate and SceneDelegate in order to refresh the scene/window?

Background data task in watchOS

I'm trying to build a basic proof-of-concept watchOS app and complication that pulls JSON data from an API and displays a gauge based on that.
I've watched Apple's 'Keping your watch app up to date' and found several other questions on the subject but the sample code has been taken down.
The API provides forecasts for the next ~48 hours and can be used to populate the timeline entries for the complication. When the Complication Controller requests the timeline entries I pull the data from the Extension Delegate and therefore it must be kept up to date. However the process of scheduling background data tasks has got me stumped. When I call backgroundSession.dataTask(with: URL(string: "https://...... I expect my URLSessionDataDelegate functions to be called but they never are and I never get a the handle(_ backgroundTasks) called with WKURLSessionRefreshBackgroundTask
Question:
When a WKApplicationRefreshBackgroundTask is sent to my ExtensionDelegate how should I request/schedule data from the API and then receive it?
Code
//ExtensionDelegate
class ExtensionDelegate: NSObject, WKExtensionDelegate, URLSessionDelegate, URLSessionDataDelegate {
...
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
print("background task as WKApplicationRefreshBackgroundTask")
self.scheduleURLSession()
backgroundTask.setTaskCompletedWithSnapshot(false)
return
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
print("background task as WKURLSessionRefreshBackgroundTask")
let backgroundConfigObject =
URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier)
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
print("Rejoining session ", backgroundSession)
self.savedTask = urlSessionTask
return
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "nz.co.craigstanton")
backgroundConfigObject.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let dataTask = backgroundSession.dataTask(with: URL(string: "https://craigstanton.co.nz/uvi-test?latitude=-36&longitude=174")!)
print("scheduleURLSession about to 'resume' ")
dataTask.resume()
}
//Delegate callbacks
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Data task error", error)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("urlSession Delegate did receive everything ")
}
func urlSession(_: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("urlSession Delegate did receive something ")
}

How to open a specific view controller from a custom notification action in WatchOS 3

I'm developing a WatchOS3 application in which the user receives local notifications with custom actions. The user has 2 custom actions that he can call on the notification, option 1 and option 2. After the user taps on either of the options, the app should launch into a specific view.
So far, the notification actions are handled correctly with this function in the ExtenionsDelegate:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
let identifier = response.actionIdentifier
print(identifier)
switch(identifier){
case "option1":
print("tapped option1")
case "option2":
print("tapped option2")
default: break
}
completionHandler()
}
And here's the code from my main InterfaceController in which the notifications categories are defined:
func actioncategories() {
let option1 = UNNotificationAction(identifier: "option1", title: "Test Option 1", options: .foreground) //Button 1
let option2 = UNNotificationAction(identifier: "option2", title: "Test Option 2", options: .foreground) //Button 2
let actioncategory = UNNotificationCategory(identifier: "action_category", actions: [option1, option2], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([actioncategory]) //setting actions & categories
}
Now how do I tell my application to launch into a specific view when either option1 or option2 is tapped?
I found a solution:
Instead of using func userNotificationCenter in ExtensionsDelegate, use func handleAction(withIdentifier identifier: String?, for notification: UNNotification) in your main interface controller
with presentController(withName: , context: ) you can open a specific view
Code (in InterfaceController):
override func handleAction(withIdentifier identifier: String?, for notification: UNNotification) {
print("Tapped in notification")
print(identifier)
switch(identifier){
case "option1"?:
print("tapped option1")
presentController(withName: "Option1_Screen", context: "segue")
case "option2"?:
print("tapped option2")
presentController(withName: "Option2_Screen", context: "segue")
default: break
}
}

Resources