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.
Related
I am currently learning iOS and one part that I find confusing is having mandatory initializations for things like UIButtons. Here is an example below.
import UIKit
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
init(backgroundColor: UIColor, title: String) {
super.init(frame: .zero)
self.backgroundColor = backgroundColor
setTitle(title, for: .normal)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure() {
//code that configures my button
}
}
So here I am creating a custom for my app. I have noticed I needed two inits.
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
and
init(backgroundColor: UIColor, title: String) {
super.init(frame: .zero)
self.backgroundColor = backgroundColor
setTitle(title, for: .normal)
configure()
}
I always assumed the first init was to actually initialize the button. It is the equivalent of me going to apple and saying "Hey, I want to be able to create a button based off of everything that UIButton has".
However, it seems that I have to have another init if I actually want to be able to customize the button. My second init is me pretty much saying "Hey, I want to be able to create a button with certain attributes such as a background color and a title". I find this kinda weird... Why do I need a separate initializer to do this? Why do I need to set the super.init(frame: .zero)? In a way, why do I even need a super.init in my custom init? Can't I pack everything into the first init?
Just in case, I was rambling. I find it confusing that we have to use two inits. I would think that we could just use one and pack everything into it. To me these two inits feel completely different and serve no purpose for each other. Any help would be much appreciated.
Thanks!
I have a subclass of UITextView, which needs to have a specific default appearance. So far, I've been able to achieve this by overriding the initialize() class function, which has been deprecated in Swift 3.1.
public class CustomTextView : UITextView {
override public class func initialize() {
self.appearance().backgroundColor = .green
}
}
Is there a way to achieve the same thing pure Swift?
I'm working around the loss of the class method initialize by using a construct like this:
class CustomTextView : UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame:frame, textContainer: textContainer)
CustomTextView.doInitialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
CustomTextView.doInitialize
}
static let doInitialize : Void = {
CustomTextView.appearance().backgroundColor = .green
}()
}
This construct has the advantage that doInitialize will be initialized only once, so the code connected with it will run only once (in this case we'll configure the appearance proxy only once); and it is early enough to affect even the first instance created (that is, every CustomTextView you ever make will in fact be green).
This question already has answers here:
Error in Swift class: Property not initialized at super.init call
(12 answers)
Closed 8 years ago.
I am getting Property self.color not initialized as super.init call error with the below code. How can I properly override the init(frame:) function please? I'd like to pass the color along with the init call.
class CircleView: UIView {
// properties
let color: UIColor
init(frame: CGRect, color: UIColor) {
self.color = color
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
...
}
}
Note that any property declared as "let" must be initialized during all possible inits. You are not doing it when the deserialization init is invoked. Thats why it does not compile.
Now that you understand the reason, let's go for the solution. You can make your constant an optional variable, so it does not need to be initialized, which should solve the problem but add another problem: now it is mutable.
If you still want to keep a let (and you probably want, otherwise you would have already defined a var), you need to decode the content from the coder that the second init receives. While doing that you must also override the serialization process and write the color value so your uiview can be properly serialized.
If you are not caring at all about serialization, the first option solves your problem. If you care about what's happening to your code, I suggest going for understanding the serialization API in Swift and implementing the proper init(decoder) and encoder method.
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.
I have a Swift UIView class (named HypnosisView) that draws a circle on the screen. The frame of the view is set to fill the screen. I would like to programmatically set the background color of the view upon initialization (so when an instance of the view is created it automatically has the specified background color). I was able to make this work with a convenience initializer, however I'm wondering if there is a more efficient way to do this (or if in fact I'm doing this correctly). In an ideal scenario, I would like to just add a piece of code that sets the background: self.background = UIColor.clearColor() to the inherited init(frame: CGRect) method, so I don't have to write a whole new initializer just to set the background color. Here is my convenience initializer method (what I'm currently using which works):
convenience init(rect: CGRect){
self.init(frame: rect)
self.backgroundColor = UIColor.clearColor()
}
and I call that method in the delegate like this:
var mainFrame = self.window!.bounds
var mainView = HypnosisView(rect: mainFrame)
Let me know if you have any questions. Thanks!
As discussed in the comments, when wanting to customize the behavior of a UIView, it's often easier to use a convenience initializer as opposed to overriding a designated initializer.
For UIViews specifically, if you override the designated init(frame aRect: CGRect), you are unfortunately also required to override init(coder decoder: NSCoder!) which is part of the NSCoding protocol. So generally if you just want to set a few properties to some default values, do as the original poster asked and create a convenience initializer that in turn calls init(frame aRect: CGRect):
convenience init(rect: CGRect, bgColor: UIColor){
self.init(frame: rect)
self.backgroundColor = bgColor
}
For a discussion on getting rid of NSCoding compliance, see Class does not implement its superclass's required members