Why does my closure property on a SKLabelNode subclass become nil? - swift

I have the following SKLabelNode subclass:
class ButtonNode: SKLabelNode {
// The only reason this is optional is because of the required init() method.
private var buttonAction: (() -> Void)?
init(_ text: String, action: #escaping () -> Void) {
self.buttonAction = action
// Initialise the button label node.
super.init(fontNamed: "Futura")
self.isUserInteractionEnabled = true
self.text = text
self.fontColor = SKColor.white
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touches.first?.location(in: self) != nil {
self.buttonAction?()
}
}
}
I then instantiate the ButtonNode using:
let button = ButtonNode("Label", action: { print("Action called") })
This doesn't work as I expect it to (the action is never called). Upon investigating I discovered that:
In my intialiser the buttonAction property is not nil
In the touchesBegan method, the buttonAction property is nil
Why does the buttonAction property get set to nil?

Related

Correct calling init function in Apple Swift 5 subclass in Cocoa application

I cannot figure out the correct format for calling the init function of NSTextField in a subclass called HyperlinkTextField.
class HyperlinkTextField: NSTextField {
var url: String = ""
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared.open(URL(string: url)!)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
isBezeled = false
}
init(string: String) {
super.init(string: string) // error: must call a designated initializer of the superclass 'NSTextField'
isBezeled = false
}
}
All classes inheriting from NSControl have two designated initializers
init?(coder: NSCoder)
init(frame: NSRect)
So you must call the latter
init(string: String) {
super.init(frame: .zero)
self.stringValue = string
isBezeled = false
}

Subclass of NSDatePicker mouseDown event changes datePicker instance

I have instantiated and initialized a subclass of NSDatePicker in AppDelegate -> applicationDidFinishLaunching and overrode mouseDown(with event: NSEvent)in the datePicker subclass. When I press the mouse button in the datePicker and break in its overridden mouseDown func the instance is not the one I instantiated in applicationDidFinishLaunching and so is not initialized.
I've tried creating the instance at different entry points thinking it might be a timing thing but I've gotten nowhere. I'm out of ideas and feeling a little feeble. Any Help?
The datePicker:
import Cocoa
class AlarmIVDatePicker: NSDatePicker {
var viewController: ViewController!
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
}
override func mouseDown(with event: NSEvent) {
let stop = 0
}
}
The ViewController:
class ViewController: NSViewController, NSWindowDelegate{
var alarmIVDatePicker: AlarmIVDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
alarmIVDatePicker = AlarmIVDatePicker()
alarmIVDatePicker.viewController = self
}
I expected I could access the values I had set but the instance is not the one I created and all the values are nil
Okay. Here's what you can do to track this down. Add this code to your AlarmIVDatePicker class:
override init(frame frameRect: NSRect) {
Swift.print("AlarmIVDatePicker being called here")
super.init(frame: frameRect)
}
convenience init() {
self.init(frame: .zero)
}
required init?(coder: NSCoder) {
Swift.print("hey, you DID drop this into a storyboard or XIB file!")
super.init(coder: coder)
}
If you set breakpoints inside these init methods, you'll catch when and where they are being called where you did not expect.

Why is deinit not called until UIView is added to parent again?

I have a UIView that am adding to a UIViewController and am generally testing de-initialization to make sure I am doing things right. But when I don't set the variable in my viewController to nil and only use .removeFromSuperView() , the deinit() method in UIView won't be called until I add the UIView another time then its called. But if I use removeFromSuperView() and set the variable to nil then deinit() is called right away. Why is that?
Here's UIView() class:
class TestView: UIView {
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
print("init is happneing")
}
deinit {
print("de init is happneing")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here's the parent ViewController :
class MyViewController: UIViewController {
var tstview : TestView?
//adding the UIView by tapping on a button
#IBAction func addView(_ sender: UIButton) {
let test = TestView()
tstview = test
tstview?.frame = CGRect(x: 50, y: 60, width: self.view.frame.width-100, height: self.view.frame.height-200)
tstview?.backgroundColor = UIColor.white
self.view.addSubview(tstview!)
}
override func viewDidLoad() {
super.viewDidLoad()
}
//removing UIView by touching elsewhere
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tstview?.removeFromSuperview()
// tstview = nil
}
}
deinit is called when no one is referencing the object. If you don't set tstview to nil, your MyViewController is still referencing it, thus deinit won't be called. When you call addView, the statement tstview = test finally removes the last reference to the old view, thus triggering the deinitializer.
You can read more about the concept of deinitialization in the Swift documentation.
If you want to be notified as soon as the view is detached, override willMove(toSuperview:) instead.
class TestView: UIView {
...
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
print("removed from parent")
}
}
}

subclassed SKLabelNode to present scene, how?

I have a subclass of SKLabelNode that's instanced three or four times, each with a name, and a destination:
//
import SpriteKit
class MenuButton: SKLabelNode {
var goesTo = SKScene()
override init() {
super.init()
print("test... did this get called? WTF? HOW/WHY/WHERE?")
}
convenience init(text: String, color: SKColor, destination: SKScene){
self.init()
self.text = text
self.color = color
goesTo = destination
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nextScene = goesTo
// .....?????????????????
// HOW DO I GET THIS TO FIND ITS SCENE...
// THEN ITS VIEW, THEN PRESENT this nextScene????
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm at a loss as to how to get at the view I need to call the scene change on.
Every SKNode has a scene property which returns the scene where the node lives.
And every SKScene as a view property, so
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nextScene = goesTo
self.scene?.view?.presentScene(nextScene)
}

Custom Button Not Working in Swift

I have a UIViewController, a Custum UIView, and a GameController. The CustumUiView is layered on top of the UIViewController and I need to seperate out the views to track clicks on the UIView. I have a custom button in the Custum UIView "Interface View" that is registering the click, but the receiver in the GameController or the UIViewController (tried both) are not registering the clicks.
Code follows and help is appreciated.
Interface View
class InterfaceView: UIView {
var resetButton: UIButton!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
resetButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
resetButton.setImage(UIImage(named: "reset"), for: UIControlState())
addSubview(resetButton)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// let touches through and only catch the ones on buttons
let hitView = super.hitTest(point, with: event)
if hitView is UIButton {
print("Captured Hit") // Stack Overflow Note: This is working.
return hitView
}
return nil
}
}
Game Controller:
class GameController
{
var interface: InterfaceView!
{
didSet
{
interface.resetButton.addTarget(self, action: #selector (GameController.reset), for: .touchUpInside)
}
#objc func reset(sender: UIButton!)
{
print("button pressed")
}
}
UIViewController:
class GameViewController: UIViewController
{
fileprivate let controller: GameController
#IBOutlet weak var GameView: UIView!
init(_ coder: NSCoder? = nil)
{
// Logic Goes Here
controller = GameController()
if let coder = coder
{
super.init(coder: coder)!
} else
{
super.init(nibName: nil, bundle: nil)
}
}
required convenience init(coder: NSCoder)
{
self.init(coder)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first as UITouch?
{
let point = touch.preciseLocation(in: GameView)
if (touch.view == GameView)
{
print("Do stuff")
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let point = touch.preciseLocation(in: GameView)
if (touch.view == GameView)
{
print("Do other stuff")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
print("Reset Stuff")
}
Resolved by in GameViewController, using didSet to set the target and selector for the button press and then using a method in GameViewController to call a method within GameController.
#IBOutlet weak var interface: InterfaceView!
{
didSet
{
interface.resetButton.addTarget(self, action: #selector (GameViewController.reset), for: .touchUpInside)
}
}