Class extends SKScene but cannot access its attributes - swift

I have a Menu class that extends SKScene, but I cannot access view, which is an attribute of SKScene. Instead I get the following error: "Instance member 'view' cannot be used on type Menu."
Is there anything special I need to do to access this? It seems like it should already be able to call that.
Here is a shortened version of the class:
class Menu: SKScene {
var blueButton: Button!
var touchNode: SKSpriteNode!
var newScene: SKScene?
override func didMove(to view: SKView) {
blueButton = self.childNode(withName: "Start") as! Button
}
...
class func transitionToScene(node: String) {
var scene = SKScene()
switch node {
case "NewGame":
scene = GameScene()
default:
debugPrint("transition to scene called on no button")
}
scene.scaleMode = SKSceneScaleMode.aspectFill
//(self.view as! SKView).presentScene(scene)
self.view.presentScene(scene)
}
The issue comes at the last line. Above it I commented out a different attempt, but this also did not work.

You have to note a difference between instance methods and type methods. Instance methods are called on an instance of a particular type. You define them like this:
class SomeClass {
func myInstanceMethod() {
}
}
Type methods, are methods that are called on a type itself. You define them by using static keyword, or as in your example class keyword (class keyword allows subclasses to override the superclass’s implementation of that method).
So, in short you should use an instance method, because self refers in your case to the current instance, not to the Menu type (just remove the class keyword).

Related

Why do I need a force downcast to work with protocol arrays with a constraint

This is my code:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {
}
let array: [Test] = [MyView()]
let view = array[0]
let myAlpha = (view as UIView).alpha //ERROR
The error is:
error: 'Test' is not convertible to 'UIView'; did you mean to use
'as!' to force downcast?
Why the force downcast? The protocol can only be adopted by UIView, therefore every element in the array array IS-A UIView, right? Is there any case it is not a UIView, hence needing the downcast?
I scratched my head and I know the answer to my own question. With only classes, this situation would never happen, but with protocols (and interfaces in Java), I could actually put in an other class inside the array, this is how:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {}
var array: [Test] = [MyView()]
let view = array[0]
class NotAUIView {}
let myFakeTestInstance = NotAUIView() as! Test
array.append(myFakeTestInstance) // Auch! Compiles but it does not conform to the protocol Test!!
It compiles, but crashes at runtime. Subclasses can adopt ofcourse the protocol Test, I did not think about it. Force-downcasting to protocols always compiles to any class without errors, but force-downcasting to other classes which will never work, will throw an warning from the compiler saying it will always fail.
Since you define your array as containing Test objects with
let array: [Test] // <-- This
Then getting any object in that array will give you an instance of Test
let view = array[0] // <-- view is of type `Test
So if you know that this object will be of type UIView (which again, the compiler does not since you told it was a Test object), then you need to force cast.
I'm guessing the compiler does not recognize the type constraint (strict as it may be) as an actual inheritance.
You could work around this by extending your protocol to make it do the type cast itself, given that within the protocol, the identity of the class is known.
#objc protocol Test where Self:UIView {}
extension Test
{
var asUIView: UIView { return self }
}
view.asUIView.alpha // will work
Note that you need to add #objc to your protocol declaration for this to work with a bridged class (UIView) which would not be necessary with a native Swift class.
If you change the type of array to [MyView] you do not need the forced cast:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {}
let array: [MyView] = [MyView()]
let view = array[0]
let myAlpha = view.alpha
Update - fixed in Swift5 (Xcode11.1)
protocol TestProtocol where Self: UIView {
func testCall()
}
extension TestProtocol {
func testCall() {
print("protocol #function")
}
}
class MyView: UIView, TestProtocol {
// if commented out then imnplementation in extension of TestProtocol
func testCall() {
print("instance #function")
}
}
let array: [TestProtocol] = [MyView()]
let view = array[0]
let myAlpha = view.alpha
view.testCall()

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`

Call external function using WatchKit force touch MenuItem

I need to implement a WatchKit force-touch MenuItem to call a saveWorkout() method that is located in a separate class that does not subclass WKInterfaceController.
I realize that every class needs at least one designated initializer. I am guessing this is the key?
Btw, my "saveSession() reached" print statement logs to the console when using the sim but not when I use a device. All other print statements log to the console even when using the device. A bit odd.
My attempts at initialization throw various errors such as:
1.fatal error: use of unimplemented initializer 'init()' for class 'DashboardController'
2.Missing argument for parameter 'context' in call
Dashboard.swift
class DashboardController: WKInterfaceController {
#IBOutlet var timerLabel: WKInterfaceTimer!
#IBOutlet weak var milesLabel: WKInterfaceLabel!
// var wSM: WorkoutSessionManager
//init(wSM: WorkoutSessionManager) {
// self.wSM = wSM
// super.init()
// }
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))
}
override func willActivate() {
super.willActivate()
print("Dashboard controller reached")
}
func saveSession() {
//wSM.saveWorkout()
print("saveSession() reached")
}
WorkoutSessionManager.swift
class WorkoutSessionContext {
let healthStore: HKHealthStore
let activityType: HKWorkoutActivityType
let locationType: HKWorkoutSessionLocationType
init(healthStore: HKHealthStore, activityType: HKWorkoutActivityType = .Other, locationType: HKWorkoutSessionLocationType = .Unknown) {
self.healthStore = healthStore
self.activityType = activityType
self.locationType = locationType
}
}
protocol WorkoutSessionManagerDelegate: class {
// ... protocol methods
}
class WorkoutSessionManager: NSObject, HKWorkoutSessionDelegate {
let healthStore: HKHealthStore
let workoutSession: HKWorkoutSession
init(context: WorkoutSessionContext) {
self.healthStore = context.healthStore
self.workoutSession = HKWorkoutSession(activityType: context.activityType, locationType: context.locationType)
self.currentActiveEnergyQuantity = HKQuantity(unit: self.energyUnit, doubleValue: 0.0)
self.currentDistanceQuantity = HKQuantity(unit: self.distanceUnit, doubleValue: 0.0)
super.init()
self.workoutSession.delegate = self
}
func saveWorkout() {
guard let startDate = self.workoutStartDate, endDate = self.workoutEndDate else {return}
// ...code...
The fatal error is (or was) caused by this line:
let wSM = WorkoutSessionManager()
That line creates a new instance of WorkoutSessionManager and calls init() on it.
Swift provides a default initializer called init() for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. But WorkoutSessionManager does not provide default values for the healthStore and workoutSession properties (and those properties are not optionals), and it provides its own initializer named init(context:), so it has no default initializer.
You need to either create your instance of WorkoutSessionManager using the designated initializer init(context:) (passing an appropriate instance of WorkoutSessionContext) or provide a default initializer for WorkoutSessionManager named init().
The precise manner in which you should do the former depends on the implementation of the rest of your app and the presentation of your DashboardController. I assume you are trying to recreate the "Fit" app shown in WWDC 2015 Session 203.
In that demonstration, the initial controller is an instance of ActivityInterfaceController, and that controller is responsible for presenting the next interface (via segues created in the storyboard). You can see the following code in the ActivityInterfaceController class:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
let activityType: HKWorkoutActivityType
switch segueIdentifier {
case "Running":
activityType = .Running
case "Walking":
activityType = .Walking
case "Cycling":
activityType = .Cycling
default:
activityType = .Other
}
return WorkoutSessionContext(healthStore: self.healthStore, activityType: activityType)
}
The function above creates and returns a new instance of WorkoutSessionContext using an instance of HKHealthStore held by the initial controller. The context returned by that function is passed to the destination interface controller for the relevant segue through awakeWithContext.
For transitions in code, you can pass a context instance using equivalent functions such as pushControllerWithName(context:) which also lead to awakeWithContext.
If your initial controller is similar to the above, you can access the passed context in awakeWithContext in your DashboardController class and use it to configure a new instance of WorkoutSessionManager:
class DashboardController: WKInterfaceController
{
// ...
var wSM: WorkoutSessionManager?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if context is WorkoutSessionContext {
wSM = WorkoutSessionManager(context: context as! WorkoutSessionContext)
}
addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))
}
// ...
}
Creating an instance of WorkoutSessionManager in that way avoids calling the (non-existent) init() initializer and permits reuse of the HKHealthStore instance. Whether that approach is open to you depends on the rest of your code and the way you are presenting your DashboardController.
Note that you should avoid creating multiple instances of WorkoutSessionManager. Use a singleton to provide a single instance of WorkoutSessionManager that is shared across your extension.

Delegation on a class in Swift

I have a delegation/initialization problem I can't seem to solve. Basically I have a storyboard with a few View controllers. Inside the storyboard there is this "View controller" which consists of a UITableview that I have connected with a DeviceListViewController class so that it populates the information. In here I have declared the following protocol:
protocol DeviceListViewControllerDelegate: UIAlertViewDelegate {
var connectionMode:ConnectionMode { get }
func connectPeripheral(peripheral:CBPeripheral, mode:ConnectionMode)
func stopScan()
func startScan()
}
and inside the class itself I have a init method like this (which is probably wrong but I didn't know what else I could do at this point):
convenience init(aDelegate: DeviceListViewControllerDelegate) {
self.init()
self.delegate = aDelegate
}
Then there is this second class that is not attached to any view controller called BLEMainViewController. It should be a singleton handling all the bluetooth actions. This means I should be able to delegate some stuff between DevicelistViewController and BLEMainViewController.
In the BLEMainViewController I have inherited the DeviceListViewControllerDelegate:
class BLEMainViewController: NSObject, DeviceListViewControllerDelegate {
var deviceListViewController:DeviceListViewController!
var delegate: BLEMainViewControllerDelegate?
static let sharedInstance = BLEMainViewController()
}
override init() {
super.init()
// deviceListViewController.delegate = self
deviceListViewController = DeviceListViewController(aDelegate: self)
}
The problem is that BLEMainViewController is not attached to any View Controller (and it shouldn't IMO) but it needs to be initialized as a singleton in order to handle all the BLE actions. Can anyone point me in the right direction (with an example preferably) on how to work around this?
I think you simply used wrong code architecture.
The BLEManager is a shared-instance, you can call it from everywhere, set it properties, and call its methods.
Its can delegate your view-controller with any predefine events you will add to its protocol and provide proper implementation
Here is some code, hope it helps
protocol BLEManagerDelegate{
func bleManagerDidStartScan(manager : BLEManager)
}
class BLEManager: NSObject {
static let sharedInstance = BLEManager()
var delegate: BLEManagerDelegate?
var devices : [AnyObject] = []
func startScan(){
delegate?.bleManagerDidStartScan(self)
//do what ever
}
func stopScan(){
}
}

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

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.