NotificationCenter handler updates variable but UILabel stays the same - swift

Using Xcode 10.1 running the app on mini iPad OS 12.1.1
I am posting a notification to NotificationCenter and the handler function updates UILabel setupProgress to show the progress of a data import.
This used to work fine but has recently stopped working, very possibly as a result of something I have done, but I can't think what.
PM in the code is a function which prints to the console - and it tells me that self.setupProgress.text is, in fact, set correctly and changes as expected as the data loads, however the corresponding UILabel does not update.
The initial UILabel text is set like this
if !self.update_type.isEmpty && self.update_type == "setup_import" {
self.setupProgress.text = "I've told the server that this is a clean install"
}
and that works fine - but then, as the import progresses, in the handler function (below) I get no updates until
import_progress == "And now the end is upon us ..."
at which point the UILabel updates correctly and everything carries on as expected.
func handleImportNotification(_ notification:Notification) {
self.setupProgress.text = import_progress
// should show something like
// `Importing F4RES : 0 of : 1395`
// `Importing F4RES : 500 of : 1395`
// etc...
PM(#line, function: #function, str1: self.setupProgress.text)
// prints the correct updates to the console
if import_progress == "And now the end is upon us ..." {
self.makeShopOptions()
self.loadingImage.stopAnimating()
self.performSegue(withIdentifier: "setup_to_splash", sender: self)
}
}
The app continues to work just fine - just without the expected UILabel updates in between.
Thanks in advance for any ideas.

You are posting a notification to the Notification Center. Would you mind showing how and when?
Assuming you're using NotificationCenter.default.post notificationName: method, the handler should take an argument of type Notification. Otherwise it won't respond to notification updates.
The handler function should look like:
func handleImportNotification(notification: Notification) { ... }
And the observer:
var observer: NSObjectProtocol?
func someFunction() {
observer = NotificationCenter.default.addObserver(forName: object: queue: using: { (Notification) in
//Update UILabel or call the associated function
}))
}
Try this.

Related

iOS 14.5 trouble updating label text during networking loop

I have a loop that updates a remote database over the network. To keep track of the updates I want to use a counter to update the text of a label after each update. The problem is that the label text only updates after the loop completes. I have tried many combinations of DispatchQueue and DispatchGroup with no success.
The code below illustrates this problem. Thank you for your help.
import UIKit
class ViewController: UIViewController {
var counter = 0
#IBOutlet weak var countLBL: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startClick(_ sender: Any) {
workloop()
}
func workloop() {
for _ in (0...3) {
networkTask()
counter += 1
countLBL.text = String(counter)
}
}
func networkTask() {
sleep(1)
}
}
Your basic assumptions and approach are wrong.
In all cases, if you execute a function, the UI changes you apply in that function will not be displayed to the screen until you exit the function and the app passes through the event loop.
Further, you should not be making synchronous network calls.
The idea that you'd have code
networkTask()
counter += 1
//update UI with info about completed network task
Is wrong. A function that performs a network task should be written to operate asynchrnously. (It will return immediately, go off and do the network task, and call a completion handler when the task is done. The code might look like this:
networkTask(completion: { results in
DispatchQueue.main.async {
// Code to display the results of the network operation to the UI
}
}
)
The idea of using async functions and completion handlers in Swift is widely covered by Apple, by lots and lots of development blogs, and by countless posts here on SO.

How to use AXObserver in Swift

I tried to get notified when another Application creates a window using the Accessibility API and AXObserver. Here's my code:
let pid = NSWorkspace.shared.frontmostApplication!.processIdentifier
var observer: AXObserver?
if AXObserverCreate(pid, { (observer, element, notification, userData) in
// Does this get executed when a notification comes in?
print(notification)
}, &observer) == .success {
// This does get printed!
print("Successfully created Observer!")
}
if AXObserverAddNotification(observer!, element, notification, nil) == .success {
// This also gets printed!
print("Successfully added Notification!")
}
CFRunLoopAddSource(RunLoop.current.getCFRunLoop(), AXObserverGetRunLoopSource(observer!), CFRunLoopMode.defaultMode)
Am I missing something? The code compiles and runs, but my calback doesn't get executed.
Does the callback get executed when a notification comes in?
see: https://developer.apple.com/documentation/applicationservices/1459139-axobservergetrunloopsource
Note that releasing the AXObserverRef automatically removes the run loop source from the run loop
your var observer: AXObserver? is deallocated so you'll never get the call. you need to keep a reference to your AXObserver somewhere.
good luck. the AX API is a pain.

SwiftUI - KV Observe completion from Combine does not get triggered

I am trying to build a VOIP app using lib called VailerSIPLib. As the library was built using Obj-C and heavily using NotificationCenter to to publish the changes the active states all over the place.
I currently at the CallView part of the project, I can manage to start, end, reject calls. However, I need to implement connectionStatus in the view which will give information about the call like duration, "connecting..", "disconnected", "ringing" etc.
The below code is all in CallViewModel: ObservableObject;
Variables:
var activeCall: VSLCall!
#Published var connectionStatus: String = ""
Initializer:
override init(){
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
}
Methods:
func setCall(_ call: VSLCall) {
self.activeCall = call
self.activeCall.observe(\.callStateText) { (asd, change) in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
#objc func listen(_ notification: Notification) {
if let _ = self.activeCall {
print(self.activeCall.callStateText)
}
}
#objc func buildCallView(_ notification: Notification) {
print("inbound call")
self.isOnCall = true
}
Problem:
It prints out every thing except the completionBlock in setCall(_:). listen(_:) function validates that the state of the activeCall is changing and I would want to use that directly, however it does not work correct all the time. It should be triggered when the call is answered with callState value of .confirmed but sometime it does. This how I will know that it is time start the timer.
Other point is, in the example project of the VialerSIPLib they used self.activeCall.addObserver(_:) and it works fine. The problem for that is it throws a runtime error at the method something like didObservedValueChange(_:) and logs An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)' is unused
which I could not find anything related to it.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)'
This is telling you what the problem is. The observe(_:options:changeHandler:) method is only incompletely documented. It returns an object of type NSKeyValueObservation which represents your registration as a key-value observer. You need to save this object, because when the NSKeyValueObservation is destroyed, it unregisters you. So you need to add a property to CallViewModel to store it:
class CallViewModel: ObservableObject {
private var callStateTextObservation: NSKeyValueObservation?
...
And then you need to store the observation:
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
You could choose to use the Combine API for KVO instead, although it is even less documented than the Foundation API. You get a Publisher whose output is each new value of the observed property. It works like this:
class CallViewModel: ObservableObject {
private var callStateTextTicket: AnyCancellable?
...
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
.sink { print("callId: \(call.callId), callStateText: \($0)") }
}
There's no specific reason to use the Combine API in your sample code, but in general a Publisher is more flexible than an NSKeyValueObservation because Combine provides so many ways to operate on Publishers.
Your error with addObserver(_:forKeyPath:options:context:) happens because that is a much older API. It was added to NSObject long before Swift was invented. In fact, it was added before Objective-C even had blocks (closures). When you use that method, all notifications are sent to the observeValue(forKeyPath:of:change:context:) method of the observer. If you don't implement the observeValue method, the default implementation in NSObject receives the notification and raises an exception.

EKEventStore.calendars is always empty after clean install

I have a weird issue on my iOS app where, after a fresh install, if I try to add calendar events (after accepting the native calendar permissions prompt), my eventStore singleton never shows any available calendars, and defaultCalendarForNewEvents is always nil.
I've tried to debug it, and the odd part is that, if I try po EKEventStore().calendars(for: .event), then I can see them all (still on fresh install).
If I then kill the app and reopen it, my eventStore singleton suddenly starts working as expected.
I'm not sure if this behaviour has been documented before, it seems very strange to me and I couldn't find other posts with this specific issue...
Hereafter a snippet of my CalendarManager class and how the eventStore singleton is declared.
class CalendarManager {
// MARK: Variables
static let sharedInstance = CalendarManager()
fileprivate init() { }
let eventStore = EKEventStore()
// further business logic...
}
And further down, the code where I encounter issues:
func createEvent(for workout: Workout, sendReminder: Bool = false) -> Bool {
// If I add a breakpoint below and check a new instance of EKEventStore,
// I can see all calendars & defaultCalendarForNewEvents,
// however this `guard` never lets us through...
guard let calendar = CalendarManager.sharedInstance.eventStore.defaultCalendarForNewEvents else { return false }
// create event with default calendar...
}
What am I doing wrong?
Make sure you aren't initializing EKEventStore before requesting calendar permissions.

os x nstextfield validation

I have a variable in my NSViewController:
dynamic var roll_rate:Double = 0.0
I attach it to my NSTextField:
Model Key Path shows error, but it is working: When i changed value in field, variable changed too. But what means:
Validates Immediately and how do i show and check validation errors for field.
I tried implement method validateRoll_rate, but it didn't call when value changed.
Generic solution (work with or without bindings) One way of dealing with this is based on the response here
Basically you use the controlTextDidChange(notification:) delegate method of NSTextField and you implement your validation code in it.
override func controlTextDidChange (notification: NSNotification) {
guard let textField = notification.object as? NSTextField else { return }
// test here, replace the dummy test below with something useful
if textField.stringValue != "expected value" {
myTextFieldOutlet.backgroundColor = NSColor.red
myErrorLabelOutlet.stringValue = "Error !!!"
} else {
// everything OK, reset the background color and error label to the normal state
....
}
}
Obviously myTextFieldOutlet is an outlet linked to your text field and myErrorLabelOutlet is an outlet to a conveniently placed label used to show errors (blank if no error should be presented)
Bindings oriented solution Be sure Validates immediately is selected in Interface Builder and implement the following method in the class where the binding is made (Tuning View Controller in your example)
override func validateValue(_ ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, forKey inKey: String) throws {
// test here, replace the dummy test below with something useful
if roll_rate > 10.0 {
throw NSError(domain: "your-domain", code: 100, userInfo: [NSLocalizedDescriptionKey: "Error, roll rate too high"])
}
}
When the error is thrown, the user will be presented with the standard sheet announcing the error and the option to cancel the change or correct it.
If Continuously updates value is selected in Interface Builder the method above will be called for each keystroke in the text field, otherwise only after pressing Enter or loosing focus.
Note: For a full understanding on how updating values through bindings work, including what Validates immediately does, see the docs here.