I am implementing a Circle class (subclass of UIView) in Swift that sets its radius in its initializer according to the frame that is passed in init(frame: CGRect) like so:
override init(frame: CGRect)
{
radius = frame.width/2.0
super.init(frame: frame)
}
I also want to ensure for the case when the circle is instantiated from Interface Builder, so I also implement 'required init(coder aDecoder: NSCoder)` (which I am forced to do by Xcode anyway).
How can I retrieve the frame property of the view that is somehow contained in aDecoder. What I want to achieve basically would look like this:
required init(coder aDecoder: NSCoder)
{
var theFrame = aDecoder.someHowRetrieveTheFramePropertyOfTheView // how can I achieve this?
radius = theFrame.width/2.0
super.init(coder: aDecoder)
}
You could compute the radius after the frame has been set by super.init():
required init(coder aDecoder: NSCoder)
{
radius = 0 // Must be initialized before calling super.init()
super.init(coder: aDecoder)
radius = frame.width/2.0
}
Martin's answer is the correct one. (Voted). You might be able to find the way that the base class encodes the frame value and extract it, but that is fragile. (It relies on private details of the implementation of the base class, which might change and break your app in the future.) Don't develop code that depends on non-public implementation details of another class, or of your base class. That's a future bug just waiting to happen.
The pattern in initWithCoder is to first call super to get the values for the ancestor class, then extract the values for your custom class.
When you do it that way, the ancestor class will have already set up your view's frame for you, and you can just use that.
Related
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.
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()
}
I want the model to be passed in by the implementing developer and I want that to be mandatory.
let model: PagingTutorialModel
init(withModel model: PagingTutorialModel) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Error: Property 'self.model' not initialised at super.init call
View controllers cannot realistically have let values unless you're assign them a default value.
let foo = 3
The problem is, when we initialize view controllers from storyboards, iOS initializes the view controller with init(coder:). We don't have an opportunity to pass values in during initialization, so we cannot have let properties.
The reason is, that in init?(coder:) you also need to set the model. Depending of you setup, one solution could be to trap in init?(coder:):
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
But doing so, you can't initialize this view controller from a storyboard. But in my opinion Storyboards are only for prototyping.
So, as a swift novice, I'm noodling around and learning as I go. Generally a little google foo or a tutorial will help me, but now I'm stumped. So if anyone could explain this to me I would be very happy.
So I'm trying to draw a circle on screen, well, a few of them actually. I found this code online;
(http://www.ioscreator.com/tutorials/drawing-circles-uitouch-ios8-swift)
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
var context = UIGraphicsGetCurrentContext();
//... various stuff to draw a circle ..//
}
}
it's a class that draws a circle when initialised like this
currentCircle = CircleView(frame: CGRectMake(0,0,100,100 ) )
I have a few questions though.
Why does it actually draw a circle? the drawRect function never gets called directly. I guess it's because we're overriding a function in UIView, and I don't understand those concepts yet.
How can a pass variables to the initialisation of that function? Say, I want to draw circles of different thickness and I want to pass an extra variable like so:
currentCircle = CircleView(frame: CGRectMake(0,0,100,100 ), thickness:10 )
How would I modify the init to accept this? adding it like this:
override init(frame: CGRec, thickness: Int) {
super.init(frame: frame)
}
triggers an error (initialiser does override a designated initialiser from its superclass)
And why all the overrides? I've tried making it a class and using the code to draw. However,
CGContextAddArc
triggers a compiler error saying the context isn't valid, so I suppose
UIGraphicsGetCurrentContext()
isn't returning anything useful.
or if anyone knows a useful resource where I can learn a bit more about overriding and initialising classes, that would be welcome.
Why does it actually draw a circle?
You're correct that you never directly call drawRect:. It's called by the system when the view needs to be redrawn. This happens when, for example, the bounds of the view change, or you call setNeedsDisplay()
How would I modify the init…
Your init should look like this:
convenience init(frame: CGRec, thickness: Int) {
self.thickness = thickness
self.init(frame: frame)
}
You can only override methods from your class's superclasses, init(frame: CGRec, thickness: Int) isn't one of them.
You should take time to read Apple's Swift documentation before embarking on tutorials. All you need to know about the language is in there.
For more info on drawing, see Defining a Custom View, here.
An elegant approach is to turn your view into a class with full Interface Builder support. That way you can directly add and configure instances of your view in Interface Builder.
To use your custom view:
Select and insert a view
In the Identity Inspector view, set the Custom Class property to your class name (CircleView)
In the Attributes Inspector view, set the thickness
There are slightly modifications to your code needed, in particular the annotations #IBDesignable and #IBInspectable:
import UIKit
#IBDesignable class CircleView: UIView {
#IBInspectable var thickness: CGFloat = 1
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
var context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, thickness)
var r = self.bounds
var ellipseRect = CGRectMake(r.origin.x + self.thickness / 2, r.origin.y + self.thickness / 2,
r.size.width - thickness, r.size.height - thickness)
CGContextStrokeEllipseInRect(context, ellipseRect)
}
}
And yes, you're right. You never call 'drawRect:' yourself. You add instances of your view to your screens and iOS will call 'drawRect:' when it needs to paint your view.
It's the overall patterns of an graphical user interfaces system: you mainly react to events such as clicks from the user or repaint events.
When inheriting from UIControl my app crashes as soon as I add a property to my UIControl Class and a hit test is performed (EXC_BAD_ACCESS) => E.g. mouse over the Control:
class ReloadButton: UIControl {
var stopRotating: Bool = true
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
init(frame aRect: CGRect){
super.init(frame: aRect)
}
}
If I remove the property stopRotating it won't crash. If I change the inheritance to UIButton instead of UIControl the crash won't happen.
Is there a specific function which needs to be added to UIControl to handle hit tests?
UPDATE: I created a minimalistic sample project on github: https://github.com/Aranir/hit_test
With the beta 5 Xcode the error messages have become more explicit.
Apparently the method
override init(){
super.init()
}
needed to be implemented for it to work. This is now also necessary if the class inherits from UIButton