How can an existing protocol implemented by a delegate be extended with a var? - swift

I've tried many combinations and the problem still remains. I can't figure out why Swift won't compile the following code. I've used multiple variations (using 'where' to constraint protocol, moving setter & getter inside the protocol, etc...) Still no luck. Can you see where the problem is?
// GameScene.swift
import SpriteKit
extension SKSceneDelegate { // adding 'where Self: Game'
// causes err to moves somewhere else
var playerDirection: PlayerDirection { get set } // doesn't like this!
}
class GameScene: SKScene {
override func keyDown(theEvent: NSEvent) {
switch (theEvent.keyCode) {
case 123:
delegate!.playerDirection = .Left;
case 124:
delegate!.playerDirection = .Right;
default:
break
}
}
}
// SomeGame.swift
import Foundation
import SpriteKit
class Game: NSObject, SKSceneDelegate {
var _playerDirection: PlayerDirection = .None
// moving that code to the protocol, compiler can't find _playerDirection
var playerDirection: PlayerDirection {
set {
_playerDirection = newValue
}
get {
return _playerDirection
}
}
lazy var scene: GameScene = {
let scene = GameScene(size: CGSizeMake(CGFloat(100), CGFloat(100)))
scene.delegate = self
return scene
}()
func update(currentTime: NSTimeInterval, forScene scene: SKScene) {
}
}
// PlayerControlComponent.swift
import Foundation
enum PlayerDirection {
case None, Left, Right, Down, Up
}

I think you're approaching your problem from the wrong angle. It looks like what you want is to be able to access playerDirection from the keyDown() function in GameScene. Instead of attempting to make playerDirection a property of the SKSceneDelegate protocol, you should probably be checking to see if the delegate property in GameScene is a Game and, if it is, casting delegate to Game so that the playerDirection property becomes available to you.
You can do that very easily with an if let and the as? operator like this:
override func keyDown(theEvent: NSEvent) {
if let game = delegate as? Game {
switch (theEvent.keyCode) {
case 123:
game.playerDirection = .Left;
case 124:
game.playerDirection = .Right;
default:
break
}
}
}
This is doubly nice because now you're also checking to make sure that delegate actually exists before using it. Forcibly unwrapping it, like you were doing before, could cause a runtime exception if delegate isn't set when before that function is called.

Related

Why is conformance to an object required for read/write extension properties to work on let variables?

I know the title may be confusing, but this should clear it up.
Say I define the following extension on UIView...
extension UIView {
var isVisible:Bool {
get { return !isHidden }
set { isHidden = !newValue }
}
}
In code, I can do this without issue...
let myView = UIView()
myView.isVisible = true
But if I try pulling out the extension into a reusable protocol (so I can apply it to both UIView and NSView without having to duplicate the code) like so...
public protocol ExtendedView {
var isHidden: Bool { get set }
}
public extension ExtendedView {
var isVisible: Bool {
get { return !isHidden }
set { isHidden = !newValue }
}
}
extension UIView: ExtendedView {}
extension NSView: ExtendedView {}
...then while I can read it like so...
let myView = UIView()
if myView.isVisible {
....
}
...This line will not compile!
myView.isVisible = true
It gives the following compile-time error...
cannot assign to property: 'myView' is a 'let' constant
To fix it, I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject, like so...
public protocol ExtendedView : AnyObject {
var isHidden: Bool { get set }
}
My question is why? I mean the compiler knows at compile time the type of item the extension is being applied to so why does the protocol have to conform to AnyObject? (Yes, I do acknowledge that extending UIView (or NSView) implies an object, but still... doesn't the call site know it's not a value type?)
doesn't the call site know it's not a value type?
That doesn't matter. Protocol members allows for mutation of self. For example, if you don't constrain the protocol to AnyObject, this will always compile:
set { self = newValue as? Self ?? self }
I.e. protocols provide the only way to be able to change a reference internally. Even though you're not actually doing that in your code, the possibility of the reference mutation is there.
And even if you don't actually cause any mutation, property observers are still going to be triggered by mutating protocol members.
var myView = UIView() {
didSet {
print("Still the same \(myView) after `isVisible` changes, but that's not provable at compile-time.")
}
}
Your particular issue is due to the default of set accessors.
{ get set }
is shorthand for
{ nonmutating get mutating set }
If you change the get to be mutating as well, you'll run into the same issue.
public protocol ExtendedView {
var isHidden: Bool { get }
}
public extension ExtendedView {
var isVisible: Bool {
mutating get { !isHidden }
}
}
// Cannot use mutating getter on immutable value: 'myView' is a 'let' constant
let myView = UIView()
myView.isVisible
I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject
Although it's not apparent why you shouldn't be constraining to AnyObject or something more restrictive, you can just use
var isHidden: Bool { get nonmutating set }
That's enough to be able to make myView a constant. However, it's more accurate to mark isVisible completely nonmutating as well, which will stop property observers triggering.
nonmutating set { isHidden = !newValue }
Ultimately, constraining as much as possible is going to make working with any protocol easier. Especially when it allows you to enforce reference semantics.
public enum OldUIFramework { }
#if os(macOS)
import AppKit
public extension OldUIFramework {
typealias View = NSView
}
#else
import UIKit
public extension OldUIFramework {
typealias View = UIView
}
#endif
extension OldUIFramework.View: ExtendedView { }
public protocol ExtendedView: OldUIFramework.View {
var isHidden: Bool { get set }
}
If you really need ExtendedView to apply to value types sometimes, then make a constrained extension for the other cases, calling the value type code.
any should be some, here, but the compiler has bugs that make it not work right now.
public extension ExtendedView where Self: OldUIFramework.View {
var isVisible: Bool {
get {
let `self`: any ExtendedView = self
return `self`.isVisible
}
nonmutating set {
var `self`: any ExtendedView = self
`self`.isVisible = newValue
}
}
}
I mean the compiler knows at compile time the type of item the extension is being applied to
I know, it looks like the compiler knows that, especially when you write the lines next to each other like that:
let myView = UIView()
myView.isVisible = true
But command-click on isVisible in that code, and where do you end up? In the protocol ExtendedView. In other words, isVisible is not ultimately a property declared by UIView; it's a property declared by ExtendedView.
And nothing about the protocol itself guarantees that the adopting object will be a reference type — unless you guarantee it by qualifying the protocol, either directly or in an extension of the protocol, by saying what kind of object can adopt it.
I would just like to add that the situation you've posited is extremely specialized: the issue only arises in exactly the situation you've created, where a protocol extension injects a computed property implementation into its adopters. That's not a common thing to do.

GKState: Why is StateMachine == nil on delegate call?

I'm developing a SpriteKit Game using the GKStateMachine for transitioning between multiple scenes. I'm using a class that holds an instance of the StateMachine, which has multiple states. The states themselves have access to the current SKView through an initializer property. The States are responsible for presenting scenes, so they have a scene property, which will be presented on the didEnter-Method.
Now I have a MainMenuState, which has a scene with 3 buttons. For passing the button events to the State, I wrote a custom delegate. The MainMenuState implements the delegate protocol and sets the scene's delegate property to 'self'. So when a user hits a button the action is forwarded to a delegate method. I wanted to use the delegate method to transition to the next state (e.g. SettingsState). However, when I try to access the GKStates StateMachine property within the delegate func it's always nil.
I think that I have a design problem here and I don't know how to solve it.
The Code of the MainMenuState
import Foundation
import GameplayKit
class MainMenuState : GKState, MenuSceneDelegate {
var view: SKView
var scene: GKScene?
init(view: SKView) {
self.view = view;
self.scene = GKScene(fileNamed: "MenuScene")
super.init()
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is MultiplayerHostState.Type ||
stateClass is MultiplayerSearchState.Type ||
stateClass is SettingsState.Type
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = self.scene {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! MenuScene? {
// Set delegate
sceneNode.menuDelegate = self
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
sceneNode.size = view.bounds.size
// Present the scene
if let view = self.view as SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
}
func hostGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerHostState.self)
}
}
func joinGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerSearchState.self)
}
}
func settingsClicked() {
// <-- Here the StateMachine is nil -->
if let stateMachine = self.stateMachine {
stateMachine.enter(SettingsState.self)
}
}
}
After some research I found out this behavior was caused by Swifts ARC System.
The class holding the reference of the state machine was only declared within a func of the overall ViewController. So after existing the func, the class and state machine were deallocated.
I solved it within the View Controller:
class ViewController {
var classWithStateMachine: ClassWithStateMachine?
func initializeClassWithStateMachine {
self.classWithStateMachine = ClassWithStateMachine()
}
}
This code snippet is just a demonstration of the concept, no real code.

Testing Delegation in Playground giving 'nil'

I have the following code in Playground -I'm learning delegation-...
import UIKit
protocol FollowThisProtocol {
func passingTheValue(aValue: String)
}
class IPassTheValues{
var aDelegate: FollowThisProtocol!
func runThisFunc(){
aDelegate.passingTheValue(aValue: "I like this game")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass.aDelegate = self
}
func passingTheValue(aValue: String) {
localString = aValue
}
}
When I attempt to
print(IReceiveTheValues().localString)
it's giving me nil
It also gives me nil if I run the following lines before attempting to print(IReceiveTheValues().localString)...
IPassTheValues()
IReceiveTheValues()
could you please help me understand why the value is not being passed from the 1st class to the 2nd..?
Or if you can spot something in my code that is contradicting itself, could you please point it out..?
Appreciate your time and help.
You need to create the IPassTheValues object before assigning yourself as the delegate, and then call runThisFunc() on the instance:
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
Then test:
// Create the `IReceiveTheValues` object
let irtv = IReceiveTheValues()
// Run the method
irtv.runReceivefunc()
// Get the resulting string
print(irtv.localString)
I suggest 2 other changes. Make your delegate weak so that you don't get a retain cycle which makes it impossible to delete either object. In order to do that, you will need to add : class to your protocol declaration because only reference objects (instances of a class) can be weak.
Here's the modified code. Try it and see what happens when you delete weak.
protocol FollowThisProtocol: class {
func passingTheValue(aValue: String)
}
class IPassTheValues{
weak var aDelegate: FollowThisProtocol!
func runThisFunc(){
print("Calling delegate...")
aDelegate.passingTheValue(aValue: "I like this game")
}
deinit {
print("IPassTheValues deinitialized")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
func passingTheValue(aValue: String) {
print("Receiving value from helper object...")
localString = aValue
}
deinit {
print("IReceiveTheValues deinitialized")
}
}
func test() {
let irtv = IReceiveTheValues()
irtv.runReceivefunc()
print(irtv.localString)
}
test()

Inherit Variables With Swift Classes

I am working on a project in swift that involves multiple Swift files, each with a class in them. My goal is to have some classes that inherit properties from the others. For some reason, I cannot access any class' variables from any other class. For example, here is one file:
class Enemy {
var ready = false
var someVal = 0
func someFunctions() {
}
}
In another file, I've tried to create a class that inherits from "Enemy"
class badGuy: Enemy {
ready = true // This doesn't work as I would expect it to
func badGuyFunction() {
}
}
If I attempt to access the variables someVal or ready from either class, I am given an error;
class randomClass {
func test() {
print(Enemy.ready) //This doesn't work
print (badGuy.ready) // This doesn't work
}
}
What am I doing wrong here? I've tried to use init() functions in each of the classes, but that doesn't work. Just to clarify, I'd like to have a base class, then have a subclass whose "type" is the base class, then in the subclass define values for each of the variables the base class supports. badGuy should automatically be able to set it's own someVal. Thanks in advance.
You're very close. With just a few minor edits it works as you intend.
Here is the new Enemy class, which is almost identical to your original.
class Enemy {
var ready = false
var someVal = 0
func someFunction() {}
}
The BadGuy subclass can set its properties in its initializer.
class BadGuy: Enemy {
override init() {
super.init()
ready = true
}
func badGuyFunction() {}
}
And then you should be able to use them like this:
let badGuy = BadGuy()
print(badGuy.ready) // prints `true`
Issue #1 occurs because you have to override ready in the init method
class Enemy {
var ready = false
}
class BadGuy: Enemy {
override init() {
super.init()
ready = true
}
}
Stored properties cannot be overridden directly.
Issue #2 occurs because you are calling the instance method on the type. You need to create instances of the classes.
class RandomClass {
func test() {
let enemy = Enemy()
let badGuy = BadGuy()
print(enemy.ready)
print(badGuy.ready)
}
}
let randomClass = RandomClass()
randomClass.test() // prints two lines `false` and `true`

Delegation: How to Pass a Property Between Custom Views

In the code below, which (I hope) includes all that's relevant to my question, a mouseEntered/-Exited event in ChangerView is supposed to change the display in ChangingView. (ChangerView and ChangingView are displayed side-by-side and share a view controller.) As an OOP newbie, though, I'm seriously missing something about how to set up delegation between these views. Here's ChangerView (in which DoThis?.show = nil, despite that I thought I was setting it to true or false):
import Cocoa
protocol DoThis { var show: Bool { get set } }
class ChangerView: NSView {
var changeDelegate: DoThis?
// Set up for mouseEntered/-Exited
override func mouseEntered(theEvent: NSEvent) { DoThis?.show = true }
override func mouseExited(theEvent: NSEvent) { DoThis?.show = false }
}
And here's changing view:
import Cocoa
class ChangingView: NSView, DoThis {
var show: Bool = false { didSet { needsDisplay = true } }
// Draw into the view
override func drawRect(dirtyRect: NSRect) {
switch show {
case true: // Display setup contingent on show = true
case false: // Display setup contingent on show = false
}
// Draw contingent display
}
}
As I understand things, views should do their own basic display work, and view controllers should handle model-related and higher-level display changes. For that reason, and to keep things simple, I want ChangerView and ChangingView to communicate directly. Unfortunately, I couldn't find any explanations about delegation close enough to this situation—at least not that I could understand.
What am I missing (besides a properly functioning brain)?
Thanks!
It looks like there are two issues.
In your ChangerView class, you should be using the delegate to set the show variable, like this:
import Cocoa
protocol DoThis { var show: Bool { get set } }
class ChangerView: NSView {
var changeDelegate: DoThis?
// Set up for mouseEntered/-Exited
override func mouseEntered(theEvent: NSEvent) { changeDelegate?.show = true }
override func mouseExited(theEvent: NSEvent) { changeDelegate?.show = false }
}
You may want to make the delegate variable weak to prevent reference cycles
The other issue is you've forgot the step where you assign the delegate. I (and I think everyone else) forget this often. Once you get used to setting up delegates you'll remember to check for it if things don't work at first.
So at some point you need to set the changeDelegate var to an instance of the ChangingView class (this is often done in the viewDidLoad() function.
It will look something like this:
class SomeViewController: UIViewController {
#IBOutlet weak var SomeChangerView: ChangerView!
#IBOutlet weak var SomeChangingView: ChangingView!
override func viewDidLoad() {
super.viewDidLoad()
SomeChangerView.changerDelegate = SomeChangingView
}