How to access widget state outside of main thread - swift

I need to read a checkbox’s state from a thread that is not the main thread. It seems I cannot simply use the following (which causes the Main Thread Checker to say “UI API called on a background thread: -[NSCell state]”):
myCheckbox.state
What am I supposed to do instead?

My current solution is to maintain a property that gets updated when the checkbox gets switched, and access that property instead of directly reading the checkbox’s state.
Something more elegant would be welcome: this is cumbersome; even more so if you want to make the property read-only or properly handle setting its value from inside the program.
class myViewController: NSViewController {
#IBOutlet private weak var myCheckbox: NSButtonCell!
public var myCheckboxState: Bool! // This can be read from any thread.
#IBAction func onMyCheckboxAction(_ sender: NSButtonCell) {
myCheckboxState = (myCheckbox.state == NSOnState)
}
override func viewDidLoad() {
super.viewDidLoad()
...
myCheckboxState = (myCheckbox.state == NSOnState)
...
}
...
}

Related

Sending data using protocols

I have issues with using protocols to send data back to previous controller. I have studied SO questions and guides, but for some reason my data doesn't get transferred back.
In my second class I create data, that is later being sent back to first class:
protocol ImageEditorDelegate {
func sendImage(image: UIImage, id: String)
}
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didPressSave(_ sender: UIButton) {
delegate?.sendImage(image: finalImage, id: imageThatWasSelected)
self.dismiss(animated: true, completion: nil)
}
}
And in my receiving class I have:
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
override func viewDidLoad() {
super.viewDidLoad()
imageEditor?.delegate = self
}
func sendImage(image: UIImage, id: String) {
print("Receiving images", image, id)
switch id {
case "1":
selectedImages[1] = image
productImage1.image = image
case "2":
selectedImages[2] = image
productImage2.image = image
case "3":
selectedImages[3] = image
productImage3.image = image
default:
break
}
}
}
But nothing happens, this func never gets called. I think my delegate is nil, or so, but how could I fix this issue? I have Also, I'm using VIPER as architecture with slightly customized segues, may this be the issue? I have tried simple segues, but had same issue.
I understand that this is rather simple question, but I couldn't understand what I doing wrong after I have read articles about protocols.
Thanks for your help!
What you're doing is very wrong. You have two view controllers with property references to one another:
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
}
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
}
Those are not weak references, so if you ever do get this to work — that is, if you ever arrange things so that the NewProductViewController's imageEditor is a PhotoEditorViewController whose delegate is that NewProductViewController — you will have a nasty retain cycle and a memory leak.
This suggests that you have not understood the protocol-and-delegate pattern. Only the presented view controller should have a delegate property pointing back to the presenter, and it should be weak. The presenter does not need any property pointing to the presented view controller, because it presents it.
you need to instantiate your photoEditor, like
photoEditor = PhotoEditorViewController()
before attempting to set its delegate.
you dont' have to do this next part, but I'd suggest making the delegate variable a weak variable to avoid any retain issues, like so
weak var delegate: ImageEditorDelegate?
and you'll need to mark the protocol as class like so
protocol ImageEditorDelegate : class {

Swift Threading: When to use DispatchQueue.main.async?

I believe I understand what the dispatch queue is doing when I call it, but I'm not sure when exactly I should use it and what it's advantages are when I do use it.
If my understanding is correct, DispatchQueue.main.async { // code } will schedule the code contained within the closure to run on the main dispatch queue in an asynchronous manner. The main queue has the highest priority, and is typically reserved for updating UI to maximize App responsiveness.
Where I'm confused is: What exactly is the difference in updating UI elements within a dispatch queue closure versus just writing the code outside the closure in the same spot? Is it faster to execute the code in the body of a view did load method rather than sending it to the dispatch queue? If not, why?
Code Example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
}
Versus:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
updateUI()
}
}
}
Which one is will update the UI faster?
The primary use of DispatchQueue.main.async is when you have code running on a background queue and you need a specific block of code to be executed on the main queue.
In your code, viewDidLoad is already running on the main queue so there is little reason to use DispatchQueue.main.async.
But isn't necessarily wrong to use it. But it does change the order of execution.
Example without:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
updateUI()
print("after")
}
}
As one might expect, the output will be:
before
update
after
Now add DispatchQueue.main.async:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
DispatchQueue.main.async {
updateUI()
}
print("after")
}
}
And the output changes:
before
after
update
This is because the async closure is queued up to run after the current runloop completes.
I just ran into the exact situation discribed in your Question: viewDidLoad() calling DispatchQueue.main.async.
In my case I was wanting to modify Storyboard defaults prior to displaying a view.
But when I ran the app, the default Storyboard items were momentarily displayed. The animated segue would finish. And only THEN would the UI components be modified via the code in viewDidLoad(). So there was this annoying flash of all of the default storyboard values before the real values were edited in.
This was because I was modifying those controls via a helper function that always first dispatched to the main thread. That dispatch was too late to modify the controls prior to their first display.
So: modify Storyboard UI in viewDidLoad() without dispatching to the Main Thread. If you're already on the main thread, do the work there. Otherwise your eventual async dispatch may be too late.

ViewController + Storyboard setting up validation with controlTextDidChange

Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.

Trying to access property of a class which subclasses a cocoa class gives run-time error

I made a customized button type(class) that inherits from NSButton and has some additional methods as well, but when I try to access the methods that I declared myself, I get a run-time error. Here's my code:
import Cocoa
class MCButton: NSButton {
func testFunc()->Bool {
return true
}
}
class ViewController: NSViewController {
#IBOutlet weak var button: MCButton!
override func viewDidLoad() {
super.viewDidLoad()
if button.testFunc() { //Thread 1: EXC_BAD_ACCESS(code=2, address=0x608000264600)
button.title = "Hi!"
}
}
}
Note that I don't have any problems when I only use the methods declared in the superclass(NSButton). What's the problem? What should I do to fix it?
You have to correctly set the button's class in the InterfaceBuilder. It probably has the predefined value set, which is NSButton. You have to set it to MCButton instead.
Only then you actually get a reference to a correct instance of one of those MCButtons.

Change #IBOutlet from a subview

I'm trying to enable or disable an #IBOutlet UIButton Item of a toolbar from a UIView.
The button should get disabled when the array that I'm using in EraseView.Swift is empty.
I tried creating an instance of the view controller but it gives me the error (found nil while unwrapping):
in EraseView:
class EraseView: UIView {
...
let editViewController = EditImageViewController()
//array has item
editViewController.undoEraseButton.enabled = true //here I get the error
...
}
I tried to put a global Bool that changed the value using it in EditImageViewController but it doesn't work:
var enableUndoButton = false
class EditImageViewController: UIViewController {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
viewDidLoad() {
undoEraseButton.enabled = enableUndoButton
}
}
class EraseView: UIView {
...
//array has item
enableUndoButton = true //here I get the error
...
}
I know it's simple but I can't let it work. Here's the situation:
The root of the problem is the line that says:
let editViewController = EditImageViewController()
The EditImageViewController() says "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." Clearly, that's not what you want.
You need to provide some way for the EraseView to inform the existing view controller whether there was some change to its "is empty" state. And, ideally, you want to do this in a way that keeps these two classes loosely coupled. The EraseView should only be informing the view controller of the change of the "is empty" state, and the view controller should initiate the updating of the other subviews (i.e. the button). A view really shouldn't be updating another view's outlets.
There are two ways you might do that:
Closure:
You can give the EraseView a optional closure that it will call when it toggles from "empty" and "not empty":
var emptyStateChanged: ((Bool) -> ())?
Then it can call this when the state changes. E.g., when you delete the last item in the view, the EraseView can call that closure:
emptyStateChanged?(true)
Finally, for that to actually do anything, the view controller should supply the actual closure to enable and disable the button upon the state change:
override func viewDidLoad() {
super.viewDidLoad()
eraseView.emptyStateChanged = { [unowned self] isEmpty in
self.undoEraseButton.enabled = !isEmpty
}
}
Note, I used unowned to avoid strong reference cycle.
Delegate-protocol pattern:
So you might define a protocol to do that:
protocol EraseViewDelegate : class {
func eraseViewIsEmpty(empty: Bool)
}
Then give the EraseView a delegate property:
weak var delegate: EraseViewDelegate?
Note, that's weak to avoid strong reference cycles. (And that's also why I defined the protocol to be a class protocol, so that I could make it weak here.)
The EraseView would then call this delegate when the the view's "is empty" status changes. For example, when it becomes empty, it would inform its delegate accordingly:
delegate?.eraseViewIsEmpty(true)
Then, again, for this all to work, the view controller should (a) declare that is conforms to the protocol; (b) specify itself as the delegate of the EraseView; and (c) implement the eraseViewIsEmpty method, e.g.:
class EditImageViewController: UIViewController, EraseViewDelegate {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
eraseView.delegate = self
}
func eraseViewIsEmpty(empty: Bool) {
undoEraseButton.enabled = !empty
}
}
Both of these patterns keep the two classes loosely coupled, but allow the EraseView to inform its view controller of some event. It also eliminates the need for any global.
There are other approaches that could solve this problem, too, (e.g. notifications, KVN, etc.) but hopefully this illustrates the basic idea. Views should inform their view controller of any key events, and the view controller should take care of the updating of the other views.