custom SKSpriteNode classes for use in .sks file? - ios

I'm making a custom SKSpriteNode Class, which I want to use in my .sks file. I want it to have #IBInspectable property. Is it possible? And how can I implement its init(coder:) method, or there is no need to implement it?

no it's not possible to use #IBInspectable, that only works with classes in the storyboard editor.
you can create your custom class with an init and instantiate it from code. If you want to instatiate your custom object from the Scene editor you MUST use the init(coder:) func
you can have both init's in your class in case you wish to instantiate your object in code at some point as well as creating in a scene sks file.
init() {
super.init(texture: nil, color: .clear, size, CGSize.zero)
setup()
}
required init?(code aDecoder: NSCoder) {
super.init(code: aDecoder)
setup()
}
func setup() {
//add some setup code here
}
Or if you ONLY want to instantiate the object in the scene file you can eliminate the normal init().

Related

When is "required init?(coder aDecoder: NSCoder)" called on a UIView or UIViewController?

When I create a subclass of UIView or UIViewController with a stored property, Xcode will not compile my project unless I include an implementation of required init?(coder aDecoder: NSCoder). Currently, I have the following implementation to shut the compiler up:
required init?(coder aDecoder: NSCoder) {
fatalError()
}
I understand why I'm required to include this initializer; my subclass needs to conform to the NSCoding protocol because its superclass conforms to it, and this initializer is part of the NSCoding protocol so it needs to work with my class, i.e. initialize all of my class's stored properties (which the superclass version of the initializer won't do).
I imagine that a correct implementation would look something like this:
class MyView: UIView {
let label: UILabel
override init(frame: CGRect) {
label = UILabel()
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
if let label = aDecoder.decodeObject() as? UILabel {
self.label = label
} else {
return nil
}
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(label)
super.encode(with: aCoder)
}
}
However, considering that my application has over 50 custom views and view controllers, correctly implementing this function in every custom view and view controller is a lot of work.
So, I'm wondering if it's necessary to implement this initializer correctly, or if I can just leave it throwing a fatal error. In other words, will this initializer ever be called if I don't call it in my own code? I think I read that it might be called by a Storyboard, but my app doesn't use any Storyboards.
This initialiser will be called if an instance of your view is used in a storyboard scene.
It is up to you whether to create a functioning initialiser or not, but it should mostly be a matter of copying code from init(frame:)
It provides an NSCoder instance as a parameter, which you need only if you are using iOS serialization APIs. This is not used often, so you can ignore it. If you are curious to learn, serialisation converts an object in a byte stream that you can save on disk or send over the network.
During the initalization of a view controller, you usually allocate the resources that the view controller will need during its lifetime. So, this include model objects or other auxiliary controllers, like network controllers.

Cannot convert value of type CGSize to expected argument type NSCoder

I am getting a couple of errors like this in a few of my programs and been trying to fix it. Example: When I put size instead of coder it will tell me its incorrect and change it back to coder.
func reset() {
self.removeAllChildren()
var viewSize = view?.bounds.size
backgrounMusicPlayer.stop()
replayButton.isHidden = true
let gameScene = GameScene(coder: viewSize) //where error is
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
gameScene.scaleMode = SKSceneScaleMode.aspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
The problem is in your initialization of the GameScene object ... as the error implies.
let gameScene = GameScene(coder: viewSize) //where error is
You are passing in a CGSize object, when this initializer expects an NSCoder object. I'm assuming that GameScene is a subclass of SKScene, you should be initializing with
let gameScene = GameScene(size: viewSize)
To avoid to write wrong syntax you could use the XCode autocompletion.
Control + SpaceBar triggers XCode’s autocomplete functionality, also when you want to know the available init methods about a generic class like SKScene you simply write:
Doing this you can immediatly see input properties and the required types.
About your case you have a class named GameScene that subclass SKScene and you try to make:
SKScene.init(coder: <#T##NSCoder#>)
Obviusly you cannot pass viewSize as the input parameter for this init method. You should use init(size: CGSize) method
Details:
Now we speak about the init(coder: NSCoder).
The procedure to save the state of your view to disk is known as serialization. The reverse is deserialization - restoring the state of the object from disk.
override func encode(with aCoder: NSCoder) {
// Serialize your object here
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
// Deserialize your object here
fatalError("init(coder:) has not been implemented")
}
These methods are needed in your custom class to speak with Interface Builder, for example objects to the storyboard, that serialize the state of that object on to disk and deserialize it when you use the storyboard but if you don't add any new properties to your subclass you can also avoid to write your own serialization and deserialization code for the subclass.
Assuming your GameScene class is a subclass of SKScene, it doesn't have an initializer with a coder label.
Just change GameScene(coder: viewSize) to GameScene(size: viewSize)
If this is a SpriteKit Project, the GameScene class does not need the size and only requires the name of the .sks file which represent the scene. (the size would be set as property on that file)
Try this, it would reset and represent the game scene again
if let scene = GameScene(fileNamed:"GAME_SCENE_FILE_NAME") {
scene.replayButton.isHidden = true
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
scene.scaleMode = .aspectFill
self.view!.presentScene(scene, transition: transition)
}
I have this line in my GameViewController.swift. It was trying to make me "correct" my code to use "coder" instead of "size" and NScoder was the type.:
let scene = GameScene(size: view.bounds.size)
I commented out this part of my code in my GameScene.Swift. That fixed my problem.
/*
required init?(coder aDecoder: NSCoder) {
// theSelf = self
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder) //VL:4/1/2015 had to comment out for some reason
}
*/
Intuition helped me figure it out. I did a search for NSCoder in my project and found this. Which essentially was changing how the GameScene inits. This project started at least as far back as xcode 6, which is probably why.

How come I can initialize a UIView without parameters, but its documentation does not have an empty initializer?

let view = UIView()
Why does this compile without an error when the only documented UIView initializer is init(frame: CGRect)?
Specifically, I am trying to write a new class that inherits from UIView, but this code throws an error:
class SquadHorizontalScrollViewCell: UIView {
init(var: FIRDataSnapshot){
super.init()
....
It says it must call a designated initializer.
UIView inherits from UIResponder which inherits from NSObject.
The NSObject.init() initializer is accessible on UIView, but not on subclasses of NSObject which replace this designated initializer.
Let's consider an example.
class A: NSObject {
init(_ string: String) { }
}
This leads to a compiler error for let a = A() - missing argument #1 for initializer because this initializer replaces the designated init() initializer for NSObject in the subclass.
You just need to define the initializer of the subclass as a convenience initializer:
class A: NSObject {
convenience init(_ string: String) {
self.init() // Convenience initializers must call a designated initializer.
}
}
let a = A() now compiles.
UIView can also compile with other designated initializers defined in the subclass, since its designated initializer is not known at compile time. As per the docs, if instantiating programmatically, init(frame:) is the designated initializer, otherwise init() is the designated initializer. This means that UIView inherits the NSObject designated initializer rather than replacing it as in the above example.
In your example:
class SquadHorizontalScrollViewCell: UIView {
init(var: FIRDataSnapshot){
super.init()
We see that the designated initializer is init(frame: CGRect), so you have to call this designated initializer instead.
A designated initializer should call its superclass designated initializer.
In this case super.init() is the designated initializer of NSObject not UIView.
It would be UIView's responsibility to call UIResponder init ,I guess it has no designated initializer, hence UIView will call Super.init in its init(frame:CGrect) initializer. check "Initializer Delegation"
for why let x = UIView() is ok , its because of this
Unlike subclasses in Objective-C, Swift subclasses do not inherit
their superclass initializers by default. Swift’s approach prevents a
situation in which a simple initializer from a superclass is inherited
by a more specialized subclass and is used to create a new instance of
the subclass that is not fully or correctly initialized. (Apple)
since UIView is objective c class it still can do it. but you won't be able to call SquadHorizontalScrollViewCell() unless you did not provide any initializer or you overrides the designated initializer of the superclass (UIView)
Check this link for more info
For UIView init(frame: CGRect) is default initializer. You must call it after initialize your instance variable. If you take view from NIB then init?(coder aDecoder: NSCoder) is called instead of init(frame: CGRect). So in that case you have to initialize your instance variable in awakeFromNib() method. In this case your code should be like this:
class SquadHorizontalScrollViewCell: UIView {
init(firDataSnapshot: FIRDataSnapshot){
// intialize your instance variable
super.init(frame: CGRectZero) // set your expected frame. For demo I have set `CGRectZero`
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
For more info you can check this link https://developer.apple.com/reference/uikit/uiview
At a certain point your view will need to be init with something, that is why the compilation is complaining, because it cannot find how to start the initialisation of your custom view. Because at the end, a view will be init from a xib (init(coder aDecoder: NSCoder)), or from a frame ( init(frame: CGFrame)). So here, the easiest way is to call super.init(frame: CGRectZero) at least in your custom init method.
init (var: FIRDataSnapshot) {
super.init(frame: CGRectZero)
}
// This method below is always needed when you want to override your init method
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
but you'll still need to set the size of your frame etc.
You'll notice if you create your own UIView subclass and only override init(frame:) with a log statement, then instantiate this new class using just init(), your init(frame:) is actually called with a zero-sized frame. So the designated initializer is still getting called.

How to add an .sks files to existing Swift/Sprite-Kit project?

I started following Ray Wenderlich's 'Space Invaders' tutorial, but have diverged considerably. I now have 3 SKScenes - my title screen, my main game screen and my end level/game over screen. The title screen and the end game scene I added and these both have .sks files; the main game screen does not and all elements (SKSpriteNodes etc) are placed programatically. The flow of my program is as follows:
I now would actually like to place some events of the main game screen via the scene editor, so I created a .sks file for it and tried to change my titleScene.swift as follows:
from:
let gameScene = GameScene(size:CGSize(width: 1536, height: 2048))
to:
let gameScene = SKScene(fileNamed: "GameScene.sks") as! GameScene!
However, this then gives:
I tried to remove the required init(coder aDecoder: NSCoder) but Xcode then complains that
required init(coder: must be supplied by subclass of SKScene
However my titleScene and gameOverScene are also sub-classes of SKScene and they don't have init(coder:)
I really can't see the difference in what I'm doing to display my titleScreen and my gameOverScene via (fileNames:) and their .sks file and trying to do the same for my gameScene.
The reason why you are getting the required is because you have variables that are not optional or not initialized before init takes place.
If you have variables that need to be assigned inside of an init function, then you would do:
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
But then you will ask me: Mr Knight0fDragon, it is telling me to replace fileNamed with coder, and it is not compiling when I switch it.
Well this is because init(fileNamed:) is a convenience init, not a designated init. In order to be able to subclass a class and get all of it's convenience inits, you need to override all of it's designated inits.
Now with SKScene, you have 3, and you already know about 1.
Let's override the other 2:
override init() {
super.init()
}
override init(size: CGSize) {
super.init(size: size)
}
Alright, now this puppy should be ready to compile, we just need to get the variables assigned.
Well what I like to do is create a setup method for any variable that has to be assigned in any version of initialization after the super is called.
Unfortunately we can't do this for constants before super is called, so those we would need to set up in each method. The reason being is that self does not fully exist yet.
This would end up looking like this:
let constant : String
required init?(coder aDecoder: NSCoder)
{
constant = "hi"
super.init(coder: aDecoder)
setup()
}
override init() {
constant = "hi"
super.init()
setup()
}
override init(size: CGSize) {
constant = "hi"
super.init(size: size)
setup()
}

Initializing UIView init AFTER its superclass init?

Looking at the a lecture slide in the Stanford iOS 9 course here, he is creating a new UIView with two initializers (one if the UIView was created from storyboard, and one if it was created in code). The following code is written at the bottom of that particular slide:
func setup() {....} //This contains the initialization code for the newly created UIView
override init(frame: CGRect) { //Initializer if the UIView was created using code.
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) { //Initializer if UIView was created in storyboard
super.init(coder:aDecoder)
setup()
}
The rule is that you must initialize ALL of your own properties FIRST before you can grab an init from a superclass. So why is it that in this case he calls his superclass init super.init BEFORE he initializes himself setup()? Doesn't that contradict the following rule:
Safety check 1 A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all its own properties are initialized before it hands off up the chain.
I haven't seen all the rest of the code in this example, but the rule is only that your properties have to be initialized (i.e. the memory they occupy has to be set to some initial value) before calling super.init(), not that you can't run extra setup code.
You can even get away with sort of not-really-initializing your properties by either declaring your properties lazy var, or using var optionals which automatically initialize to nil. You can then set them after your call to super.init().
For example:
class Foo: UIView {
var someSubview: UIView! // initializes automatically to nil
lazy var initialBackgroundColor: UIColor? = {
return self.someSubview.backgroundColor
}()
init() {
super.init(frame: .zero)
setup() // do some other stuff
}
func setup() {
someSubview = UIView()
someSubview.backgroundColor = UIColor.whiteColor()
addSubview(someSubview)
}
}

Resources