makeFirstResponder does not always fire - swift

I have an NSSearchField inside a NSToolbar that I am attempting to set makeFirstResponder on but it is working intermittently. At times the NSSearchField will become the first responder without the call to makeFirstResponder and makeFirstResponder is returning true as if it were set successfully. Setting NSWindow.initialFirstResponder has also failed to work.
class ViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()
view.window?.makeFirstResponder(view.window?.windowController?.searchField
}
}
I have had consistent working results by delaying the code with a timer but this is a less than ideal solution.
class ViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
self.view.window?.makeFirstResponder(self.windowController?.searchField)
}
}
}

If makeFirstResponder is returning true, then the it likely was made the first responder for at least a short amount of time.
You can use the fact that NSWindow.firstResponder is KVO compliant in order to detect any changes to it with something like the following code in your ViewController class:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.addObserver(self, forKeyPath: "firstResponder", options: [.initial, .new], context: nil)
self.view.window?.makeFirstResponder(self.windowController?.searchField)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "firstResponder" {
print("First responder of window: \(object) is \(change?[NSKeyValueChangeKey.newKey])")
}
}

I found a blog that led me to find the reason why this was happening. By default in macOS an NSWindow has an isRestorable Boolean value that will recall whatever the last firstResponder was regardless of what is set as an initialFirstResponder or what is set inside viewDidAppear, etc.

I found that calling webView.makeFirstResponder() didn't do anything.
But calling view.window?.makeFirstResponder(webView) did.
no idea why.
hours of frustration.

Related

window.windowController is nil inside windowWillClose() but it isn't inside viewDidAppear()

I've tried, without success, respond to events such as windowWillClose() and windowShouldClose() inside NSWindowController (yes conforming to NSWindowDelegate).
Later, to my surprise, I was able to receive those events if I make my contentViewController (NSViewController) conform to NSWindowDelegate.
Unfortunately, later on, found out that view.window?.windowController is nil inside windowWillClose() or windowShouldClose(), code:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
self.view.window?.windowController // not nil!
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // nil!!
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // nil!!
return true
}
After realizing that view.window?.windowController is not nil inside viewDidAppear() the next thing I thought was that Swift garbage collected the controller, so I changed viewDidAppear() in a way that creates another reference of windowController thus preventing garbage collection on said object, code:
var windowController: NSWindowController?
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
windowController = view.window?.windowController
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // NOT nil
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // NOT nil
return true
}
My hypothesis turned out to be correct (I think).
Is this the same issue that is preventing me from receiving those events inside NSWindowController?
Is there another way I can achieve the same thing without creating more object references?
In order to post code, I use the Answer option even though it is more of a comment.
I added in NSViewController:
override func viewDidAppear() {
super.viewDidAppear()
parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC. // The NSWC class, which conforms to NSWindowDelegate
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
I get print log:
viewDidAppear() windowController Optional()
and notification is passed.
But if I change to
override func viewDidAppear() {
super.viewDidAppear()
// parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
by commenting out parentWindowController, notification don't go anymore to the WindowController…
Edited: I declared in ViewController:
var parentWindowController: NSWindowController? // Helps keep a reference to the controller
The proposed solutions are, in my opinion, hacks that can cause serious problems with memory management by creating circular references. You definitely can make instances of NSWindowController work as the window’s delegate. The proper way is to wire it up correctly in either code or in Interface Builder in Xcode. An example of how to do it properly is offered here.
If the delegate methods are not called is because the wiring up is not done correctly.
Another thing that must be done in Swift is when you add the name of the NSWindowController subclass in Interface Builder in Xcode is to check the checkbox of Inherits from Module. If you fail to do this, none of your subclass methods will be called.

MacOS uses KVO to perform multiple executions [duplicate]

This question already has answers here:
KVO broken in iOS 9.3
(3 answers)
Closed 5 years ago.
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("in viewDidLoad");
// addObserver keyPath
UserDefaults.standard.addObserver(self, forKeyPath: "testKey", options: .new, context: nil);
print("out viewDidLoad");
}
deinit {
// removeObserver keyPath
UserDefaults.standard.removeObserver(self, forKeyPath: "testKey");
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("in observeValue keyPath: \(keyPath) value: \(UserDefaults.standard.integer(forKey: "testKey"))");
// 1. If I execute the func click () method, it will be executed two times
// 2. If App originally existed "testKey", then func observeValue () will be executed after the viewDidLoad is finished.
}
#IBAction func click(_ sender: NSButton) {
UserDefaults.standard.set(arc4random(), forKey: "testKey");
}
}
The above code is all of my test code. I used KVO in my own project, but found repeated execution.
// 1. If I execute the func click () method, it will be executed two times
// 2. If App originally existed "testKey", then func observeValue () will be executed after the viewDidLoad is finished.
This is not what I understand about KVO. My idea is that after addObserver, my observeValue will be called if my key is changed. But it didn't turn out that way. I tried to find the answer to the forum, and I didn't find the answer. I just found a similar question.
If I press Button in my view, then the final result will be..:
in viewDidLoad
out viewDidLoad
in observeValue keyPath: Optional("testKey") value: 4112410111
in observeValue keyPath: Optional("testKey") value: 3712484288
in observeValue keyPath: Optional("testKey") value: 3712484288
macos: 10.12.6 (16G29)
xcode: 9 beta6、xcode 8.3.3
If you have the same problem, please tell more people to help us solve it. Thank you
I have sent the same question to the official, and if there is a solution, I will return it here.
From setting a breakpoint in observeValue() and looking at the trace, it appears that the observations are getting fired in two places; one during click() as an effect of the line where you tell UserDefaults to set the value, and another later on, scheduled on the run loop so it happens after click() has already returned, when the system detects that the value has changed. This double notification could probably be considered a bug, since the latter notification should render the former unnecessary, and I'd consider filing a radar report on it.
Unfortunately, I can't see any way to disable this behavior. I can think of a workaround, but it's extremely hacky, kludgey, ugly, and I probably wouldn't actually do it unless the need is absolutely dire. But here it is:
private var kvoContext = 0
private let ignoreKVOKey = "com.charlessoft.example.IgnoreKVO"
// If this can be called from a thread other than the main thread,
// then you will need to take measures to protect it against race conditions
private var shouldIgnoreKVO = false
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.kvoContext { // always, always use a context pointer
if !shouldIgnoreKVO { // if this is a notification we don't want, ignore it
print("in observeValue keyPath: \(String(describing: keyPath)) value: \(UserDefaults.standard.integer(forKey: "testKey"))");
}
} else {
// call super if context pointer doesn't match ours
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
#IBAction func click(_ sender: NSButton) {
// we don't need this notification, since we'll get the later one
// resulting from the defaults having changed
self.shouldIgnoreKVO = true
defer { self.shouldIgnoreKVO = false }
UserDefaults.standard.set(arc4random(), forKey: "testKey");
}
Again, it's ugly, it's hacky, I probably wouldn't actually do it. But there it is.

Subclassing NSArrayController causes objects from ManagedObjectContext doesn't show in NSTableView

That's very strange. I have a Model with three Entities. Like this:
In InterfaceBuilder I made NSArrayController connected to MOC via RepresentedObject to ViewController. Everything works, I can add and delete Master objects, select them, I can bind to TableView and edit them. But if I subclass NSArrayControler to MasterController and add just observer:
class MastersController: NSArrayController {
override func awakeFromNib() {
self.addObserver(self, forKeyPath: "selection", options: NSKeyValueObservingOptions.old, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
Swift.print("observing", keyPath ?? "<no path>")
switch keyPath! {
case "selection":
Swift.print("selection Changed")
default: break
}
}
TableView doesn't see already existing objects, only just added. I can edit them. But when I open the document again newly added objects disappear too. If I will change the class of controller back to NSArrayController I can see them all again.
Any help?
I'm almost sure observeValue(forKeyPath:of:change:context:) is used internally by NSArrayController and you should call super.observeValue(forKeyPath:of:change:context:) to get the expected behaviour...
The problem was solved by calling super.awakeFromNib() in overrided func awakeFromNib()

KVO not working (swift 2.2)?

In my app, OneVC is one of child ViewControllers of PageViewController, TwoVC is the embed view controller of OneVC's Container View.
When the user drag the scroll view in OneVC, I want the drag action can be not just update the content in OneVC from web API, but notify TwoVC to update too.
Both OneVC and TwoVC will appear at interface at the same time when launch.
I'm following Apple's "Using Swift with Cocoa and Objective-C" "Key-Value Observing" instruction to imply KVO, but no notification is sent when the observed property changes. Please see below my code:
OneVC is the object to be observed.
class OneVC: UIViewController, UIScrollViewDelegate {
dynamic var isDragToUpdate = false
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -150 {
if isDragToUpdate {
isDragToUpdate = false
} else {
isDragToUpdate = true
}
print(isDragToUpdate)
}
}
}
TwoVC is the observer
class TwoVC: UIViewController {
let oneVC = OneVC()
override viewDidLoad() {
oneVC.addObserver(self, forKeyPath: "isDragToUpdate", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("hoh")
guard keyPath == "isDragToUpdate" else {return}
print("hah")
}
deinit {
oneVC.removeObserver(self, forKeyPath: "isDragToUpdate")
}
}
I checked row by row, and find many other stackoverflow answers, but still no idea what's going wrong on my code, when drag and release the scrollview, none of "hoh" and "hah" are print in console, except print(isDragToUpdate) is printed properly.
Thank you in advance!

NSNotificationCenter: Removing an Observer in Swift

I have a view controller with a button. When the button is pressed it adds an observer, like so:
func buttonPress(sender:UIButton){
NSNotificationCenter.defaultCenter().addObserverForName("buttonPressEvent", object:nil, queue:nil, usingBlock:{(notif) -> Void in
// code
})
}
When I dismiss this view controller, and then return to it and press the button the //code is executed twice. If I go away and come back again the //code is executed three times, and so on.
What I want to do is to remove the Observer before I add it again, so this code doesn't execute twice. Ive gone through the documentation here and Ive added this line of code just above where I add the Observer:
NSNotificationCenter.defaultCenter().removeObserver(self, name:"buttonPressEvent", object:nil)
But this isnt working.
Can anyone tell me where I'm going wrong?
When you use the 'blocks' based approach to observing notifications then self isn't in fact the observer. The function returns an object which acts as the observer:
func addObserverForName(_ name: String?,
object obj: AnyObject?,
queue queue: NSOperationQueue?,
usingBlock block: (NSNotification!) -> Void) -> NSObjectProtocol
You need to keep a reference to this returned object and pass it in as the observer when you call removeObserver
It's explained well in the Apple Doc here
Implemented it like this, seems to be working fine.
override func viewDidLoad()
{
super.viewDidLoad()
AddScreenShotNotification()
}
func AddScreenShotNotification() {
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(MyViewController.ScreenShotTaken),
name: UIApplicationUserDidTakeScreenshotNotification,
object: nil)
}
func ScreenShotTaken()
{
// do something
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}