I am trying to do KVO on an property of SKView but its not working.
I have tried it with .frame and it works like a charm, so why not also on .window ?
To clarify, I am using SpriteView in a SwiftUI app and I am trying to get the bounds of the View.
What I am after is to get the following before the App starts;
print (self.convertPoint(fromView: .zero) ). When I use this in
override func didMove
I'll get Nan/NaN. However Apple Code Level Support said this about using SpriteView and getting the bounds of a view.
The reason you are receiving NaN is that you are calling these methods
before the underlying SKView has been actually presented by SwiftUI,
which is an event that you have no visibility into, and no way to call
code when it happens.
However, when this event does occur, the SKScene’s view property will
have it’s window set from nil to a UIWindow. Therefore, you could use
KVO to observe when the window property is changed, and then make your
calls to convertPoint once there is a non-nil window.
I have so far this:
override func didMove(to view: SKView) {
observe.observe(object: view )
}
class Observer:SKScene {
var kvoToken: NSKeyValueObservation?
func observe(object: SKView) {
kvoToken = object.observe(\.window , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("NEW", self.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
I have also tried to add an observer like so :
NotificationCenter.default.addObserver(view.window, selector: #selector(test(_:)), name: NSNotification.Name(rawValue: "TestNotification"), object: nil)
The above doesn't seem to do anything. So I am kinda stuck, any help would be appreciated.
The answer (probably) ..
I couldn't read the '.window' property of UIView, so I started to look for another observable property that would change as soon as UIWindow is != nil. I think I have found it in SKScene.view.frame . I am not entirely sure that this is a 100% good answer but it works.
class Observer: NSObject {
dynamic var kvoToken: NSKeyValueObservation?
func observe(object: SKScene ) {
kvoToken = object.observe(\.view?.frame , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("CONVERTING", object.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
override func didMove(to view: SKView) {
viewer = view.scene
observe.observe(object: viewer )
}
Related
I've successfully set my first view controller to STPAddCardViewController. I now need to get the user information in the STPPaymentCardTextField. Problem is, I'm used to using the storyboard to make outlets. How do I detect the STPPaymentCardTextField programmatically?
I've tried:
class ViewController: STPAddCardViewController, STPPaymentCardTextFieldDelegate {
let paymentCardTextField = STPPaymentCardTextField()
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print(paymentCardTextField.cardNumber)
//ERROR: printing nil in the console
}
}
But I'm getting nil as an output. Any help?
You should use either STPAddCardViewController, or STPPaymentCardTextField, not both. The SDK's ViewControllers are not designed to be extended. The intended use is:
class MyVC : STPAddCardViewControllerDelegate {
override func viewDidLoad() {
…
let addCardView = STPAddCardViewController()
addCardView.delegate = self
// Start the addCardView
self.navigationController.pushViewController(addCardView, animated: true)
}
…
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreatePaymentMethod paymentMethod: STPPaymentMethod, completion: #escaping STPErrorBlock) {
// TODO: do something with paymentMethod
// Always call completion() to dismiss the view
completion()
}
func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
// TODO: handle cancel
}
}
But rather than my partial example I'd recommend reading these docs and trying out this example iOS code. Best wishes!
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.
The simple Swift 4 example below should stop when the computer's display goes to sleep.
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
asleep = true
}
}
let observer = Observer ()
observer.addDNC ()
while (!observer.asleep) {}
print ("zzzz")
However, the program gets stuck in the while loop. What am I doing wrong, and what is the proper way to wait for a Notification?
I have tried using a selector (#selector (notificationRecieved), with #objc in the function declaration, of course), to no avail.
Start a template app in Xcode and modify the ViewController.swift to do this:
import Cocoa
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
print("got sleep notification!")
asleep = true
}
}
class ViewController: NSViewController {
let observer = Observer ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
observer.addDNC ()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The difference between your code and mine is that I'm not doing the wacky sleepy polling thing you're doing (that's going to lead to a spinning pizza cursor), and I'm also setting observer to be a property off the ViewController object, so the observer property sticks around as long as the view controller does.
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!
I am new to OS X app development. I manage to built the NSComboBox (Selectable, not editable), I can get it indexOfSelectedItem on action button click, working fine.
How to detect the the value on change? When user change their selection, what kind of function I shall use to detect the new selected index?
I tried to use the NSNotification but it didn't pass the new change value, always is the default value when load. It is because I place the postNotificationName in wrong place or there are other method should use to get the value on change?
I tried searching the net, video, tutorial but mostly written for Objective-C. I can't find any answer for this in SWIFT.
import Cocoa
class NewProjectSetup: NSViewController {
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
let comBoxID = projNewRouter.indexOfSelectedItem
print(“Combo Box ID is: \(comBoxID)”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxRouterValue,myObj:projNewRouter)
self.projNewRouter.selectItemAtIndex(0)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(
self,
selector: “testNotication:”,
name:"NotificationIdentifier",
object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: projNewRouter.indexOfSelectedItem)
}
func testNotication(notification: NSNotification){
print("Found Combo ID \(notification.object)")
}
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for var i = 0; i < myValno; ++i{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You need to define a delegate for the combobox that implements the NSComboBoxDelegate protocol, and then use the comboBoxSelectionDidChange(_:) method.
The easiest method is for your NewProjectSetup class to implement the delegate, as in:
class NewProjectSetup: NSViewController, NSComboBoxDelegate { ... etc
Then in viewDidLoad, also include:
self.projNewRouter.delegate = self
// self (ie. NewProjectSetup) implements NSComboBoxDelegate
And then you can pick up the change in:
func comboBoxSelectionDidChange(notification: NSNotification) {
print("Woohoo, it changed")
}