Combine: Problem when replace addObserver with Notification publisher - combine

i try to replace the addObserver method with Notification publisher
But i cant make it work , hope someone can help
i try to setup the publisher under viewDidLoad and receive notification from another swiftui . Since i cant make it work ,So I make it under viewWillAppear within the same swift file for debugging . In the below coding , I can only receive it from the old school method and the method from viewWillAppear not from the viewDidLoad . The setup in viewDidLoad not working and if i miss out let temp2 = under viewWillAppear , it didnt work too . Is that a bugs for combine?
Result is
BBBB
CCCC
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: .dismissSwiftUI, object: nil, queue: .main) { (_) in
print("BBBB")
}
let abc = NotificationCenter.default.publisher(for: .dismissSwiftUI).sink { (_) in
print("AAAA")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// self.checkIfProfitSetup()
let temp2 = NotificationCenter.default.addObserver(forName: .dismissSwiftUI, object: nil, queue: .main) { (_) in
print("CCCC")
}
NotificationCenter.default.post(name: .dismissSwiftUI ,object: nil)
}

The .sink operator returns an AnyCancellable. So your abc property is an AnyCancellable.
The AnyCancellable documentation says
An AnyCancellable instance automatically calls cancel() when deinitialized.
Since abc is a local property, it is deinitialized when viewDidLoad returns. So your subscription is cancelled when viewDidLoad returns.
You need to store the AnyCancellable in an instance property so that it survives. Cancellable defines a store method that helps.
I usually call them “tickets” in my code, because “cancellable” is too verbose. Thus:
private var tickets: [AnyCancellable] = []
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: .dismissSwiftUI, object: nil, queue: .main) { (_) in
print("BBBB")
}
NotificationCenter.default.publisher(for: .dismissSwiftUI)
.sink { _ in print("AAAA") }
.store(in: &tickets)
}

Related

watchOS 8 HealtKit background delivery stops working after a few hours in background

I'm trying to implement background delivery of the HealthKit data for an independent watchOS 8 app. I was following Gettings the most out of HealthKit WWDC talk and seems to have added everything that is needed for background delivery to work, including recent iOS 15 and watchOS 8
com.apple.developer.healthkit.background-delivery entitlement. But for some reason, background delivery stops working after approximately 3-5 hours after the app went to the background. For example, I'm receiving updates during the evening from the app, but then over the night updates stops delivering and I'm getting those only if I open the app again in the morning. See the ExtensionDelegate code below
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private let healthStore = HKHealthStore()
private var anchor: HKQueryAnchor?
func applicationDidFinishLaunching() {
print("application did finish launching")
activateHeathKit()
}
func activateHeathKit() {
let types = Set([HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent)!])
healthStore.requestAuthorization(toShare: nil, read: types) { [weak self] success, _ in
guard let `self` = self else {
return
}
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {
return
}
`self`.healthStore.enableBackgroundDelivery(for: lowHeartRateType, frequency: .immediate) { success, _ in
print("enableBackgroundDelivery: \(success) for lowHeartRateEvent")
}
let query = HKObserverQuery(sampleType: stepsType, predicate: nil) { _, completionHandler, error in
`self`.updateLowHeartRate {
completionHandler()
}
}
`self`.healthStore.execute(query)
}
}
func updateLowHeartRate(completionHandler: #escaping () -> Void) {
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {return}
let anchoredQuery = HKAnchoredObjectQuery(type: lowHeartRateType, predicate: nil, anchor:
self.anchor, limit: Int(HKObjectQueryNoLimit)) { [unowned self] query, newSamples,
_, newAnchor, error -> Void in
for item in newSamples ?? [] {
let date = item.startDate
let hour = Calendar.current.component(.hour, from: date)
let minute = Calendar.current.component(.minute, from: date)
let message = "Low heart rate from \(hour):\(String(format: "%02d", minute))"
print(message)
}
self.anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
}
I don't see an implementation of the handle(_:) method for background tasks but perhaps it is just not shown. Link to the docs here.
Just in case here is how I have my workout app set up to update complications on the watch face.
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
if WKExtension.shared().applicationState == .background {
if let watchComplication = task as? WKApplicationRefreshBackgroundTask {
// do background work here
}
}
task.setTaskCompletedWithSnapshot(false)
}
completePendingTasksIfNeeded()
}

UpdateApplicationContext Not Being Called

It's my first time trying WatchConnectivity kit. When I attempt to send ApplicationContext from phone, my debug print indicates that updateApplicationContext is working correctly (with isPaired/reachable/isActivated/isWatchAppInstalled checked before sending the update). However, I'm having trouble receiving it form the paired watch simulator as nothing is shown on that end. Here are the code for the Phone Extension:
func sendDataToWatch(data: String) {
state.append(data)
do {
NSLog("sent by phone")
try WCSession.default.updateApplicationContext(["currentstate": state])
}
catch {
print(error)
}
}
Here are the code for watch:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
print("Hello")
if let getState = applicationContext["currentstate"] as? [String]{
print("\(getState)")
self.state = getState[0]
}
}
Any suggestion would be appreciated!

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
}
}
}

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 set up a minimal CKSubscription?

I want to set up a simple CKSubscription that notifies me a recordType was created, how?
After experimenting a while this is how to setup a minimal CKSubscription. You have to test it on Device, push notification does not work on simulator. You can add record in Dashboard, that will trigger push notification too.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert, categories: nil))
application.registerForRemoteNotifications()
return true
}
func someFunc() { // <- you need to call once in app life time, and again when it was removed / installed
let defaultContainer = CKContainer.defaultContainer()
let publicDatabase = defaultContainer.publicCloudDatabase
let subs = CKSubscription(recordType: "xxx", predicate: NSPredicate(value: true), subscriptionID: "yyy", options: .FiresOnRecordCreation)
subs.notificationInfo = CKNotificationInfo()
subs.notificationInfo.alertBody = "New item added"
publicDatabase.saveSubscription(subs, completionHandler: {subscription, error in})
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]!)
{
// <- this method will invoked
}
Did you register for notifications? You should have something like this in your application didFinishLaunchingWithOptions:
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Badge | .Sound, categories: nil))
application.registerForRemoteNotifications()
I created a working demo that is available at https://github.com/evermeer/EVCloudKitDao

Resources