Of course, in a scene...
class DotScene: SKScene {
override func didMove(to view: SKView) {
print("this scene is now in place...")
you know the scene is in place when didMove#to is called.
(It's just like ViewDidAppear, you could say.)
However
I have no clue how to know that a sprite has been added to a scene.
class Spaceship: SKSpriteNode {
func wtf() {
this sprite has just been added to a scene
(such as with .childNode)
this sprite is now in place in the scene ...
There simply - has to be - a call that alerts you that a node has appeared on the scene successfully.
What is it ?
In SpriteKit there is no way to detect inside of a custom sprite class when a node is added to a scene. This is has been left out because you have control when a sprite is added to a scene via addChild or moveToParent.
Spritekit does not conform to an MVC architecture, where as UIKit does. One of the reasons didMoveToView exists is because the purpose of the View is to be an output display only. The controller is responsible for handling the code behind for the View. Now the view controller can be used to call presentScene from view, but if we are transitioning, we really have no idea at what point the scene is officially attached to the view. (There are other reasons besides this, I am just giving one example)
Now to get around this issue, you may be able to implement Key Value Observing (KVO), and listen to when scene gets set. To do this, simply do:
override init()
{
super.init()
addObserver(self, forKeyPath: #keyPath(SKNode.scene), options: [.old, .new, .initial], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
if keyPath == #keyPath(SKNode.scene) && self.scene != nil
{
didAttachToScene()
}
}
func didAttachToScene()
{
}
deinit
{
removeObserver(self, forKeyPath: #keyPath(SKNode.scene))
}
Now, I have not tested this, I am currently in a windows environment, but if this does not work, you can listen to when a node is attached to a parent (which I have actually done with KVO so I know it is possible), and infer if we are on a scene that way since a node must have a parent to even qualify being on a scene.
override init()
{
super.init()
addObserver(self, forKeyPath: #keyPath(SKNode.parent), options: [.old, .new, .initial], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
if keyPath == #keyPath(SKNode.parent) && self.scene != nil
{
didAttachToScene()
}
}
func didAttachToScene()
{
}
deinit
{
removeObserver(self, forKeyPath: #keyPath(self.parent))
}
Related
Ive been trying to add an observer to listen to AVPlayer's "timeControlStatus", mostly taken dirrectly from Apple's example;
https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/observing_playback_state
I created a sperate class called Play and im calling the below from the ViewController
Play().playMusic(url: url!)
Class Play()
import Foundation
import AVFoundation
var player: AVPlayer! = nil
var playerItemContext = 0
class Play: AVPlayer {
func playMusic(url : URL) {
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
if player == nil {
player = AVPlayer(playerItem: playerItem)
if player.status.rawValue == 0 {
player.play()
player.addObserver(player, forKeyPath: "timeControlStatus", options: [.old, .new], context: &playerItemContext)
}
} else {
player.replaceCurrentItem(with: playerItem)
player.play()
}
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == "timeControlStatus" { print("Result") }
}
}
The above always crashes with;
<AVPlayer: 0x6000030a4770>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: timeControlStatus
Observed object: <AVPlayer: 0x6000030a4770>
Change: {
kind = 1;
new = 1;
old = 1;
}
Context: 0x1003f3e98'
If I remove the 'addObserver', the code acts as intended and plays the audio file, the weird thing is, if move all the observer code from the Play class over to ViewContoller it works? what gives?.
The difference is merely that when you move the code to the view controller, the view controller persists. It is a stable object living in the view controller hierarchy. So it lives long enough to do some work.
But on the other hand, in this line:
Play().playMusic(url: url!)
...the Play instance is created and immediately goes out of existence again, like a quantum virtual particle. It doesn't live long enough to be there when the playing proceeds. Hence the crash: you have allowed the observer to go out of existence too soon.
If you wanted your Play instance to persist, you would need to assign it to some long-lived variable, such as a property of your view controller.
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.
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.
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()
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!