Can I subclass UIView more concisely in Swift? - swift

When subclassing UIView in Swift, you are required to have init functions
override init(frame: CGRect)
{
super.init(frame:frame)
initMyStuff()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder:aDecoder)
initMyStuff()
}
override func awakeFromNib()
{
super.awakeFromNib()
initMyStuff()
}
internal func initMyStuff()
{
// Init class variables and setup constraints here
}
Surely there is a better way to do this. At the very least, what would a better naming convention be for that initMyStuff function? Maybe there is a way extend UIView to have a single custom initializer function I can override in my custom classes?

You can create a convenience initializer like so :)
convenience init(someValue: String) {
self.init(frame : CGRectZero)
.....
}

Related

How to initialize an empty object of a custom UIImageView subclass

I'm having a custom UIImageView subclass which runs some basic code like creating a shadow etc.
When I don't override any init everything works fine. I can create an empty CustomImageView() the same way I would create am empty UIImageView() . The moment I'm adding all the required overrides I cannot create an empty object anymore.
The error I am getting is:
Cannot invoke initializer for type 'ProfilePictureImageView' with no
arguments
with the suggestion:
Overloads for 'ProfilePictureImageView' exist with these partially matching parameter lists: (coder: NSCoder), (frame: CGRect), (image:
UIImage?)
I create the empty object in my view controller like this:
var customImageView = CustomImageView()
And my custom UIImageView subclass looks like this:
class CustomImageView: UIImageView {
override init(image: UIImage?) {
super.init(image: image)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
}
// Followed by my custom variables and methods.
}
I've tried to add the following, assuming I'm missing an empty init of some sort. But this didn't work either.
override init() {
super.init()
}
You've to make a convenience init method.
class CustomImageView: UIImageView {
//...
convenience init() {
self.init(frame: .zero) // or self.init(image: nil)
}
}

How to Pass extra parameters to a custom UIView class for initialization in swift

I'm trying to write a class that is of type UIView, but on initialization I want it to take an extra parameter, but I can't figure out how to get around the UIView needing its params instead. Any help is much appreciated!
class MenuBar: UIView {
let homeController: HomeController
init(controller: HomeController){
homeController = controller
super.init()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In the ViewController I'm initializing it like this:
let menuBar: MenuBar = {
let mb = MenuBar(controller: self)
return mb
}()
Try this.
class MenuBar: UIView {
let homeController: HomeController
required init(controller: HomeController){
homeController = controller
super.init(frame: CGRect.zero)
// Can't call super.init() here because it's a convenience initializer not a desginated initializer
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
From my experience this is what works best if you want to have custom initialiser for UIView:
class CustomView : UIView {
private var customProperty: CustomClass
required init(customProperty: CustomClass) {
super.init(frame: .zero)
self.customProperty = customProperty
self.setup()
}
required override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
fileprivate func setup() {
//Here all custom code for initialisation (common for all creation methods)
}
}
This approach allows you to keep common initialisation code regardless of method of creating the view (both storyboard and code)
That's about creating UIView properly.
Additionally I would recommend to avoid passing UIViewController to UIView - I think you are trying to solve some problem in a wrong way.
Much better ways to communicate between those two is to use delegate or closure - but that's a bit off-topic - maybe you can create another question about why you want to pass it like this.

Must call a designated initializer error

import UIKit
class RightAnswerButtonClass: UIButton {
var rightAnswer: Bool
init() {
super.init()
rightAnswer = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am trying to create a custom class button with the variable "rightanswer" stored as a bool. However, when I try to build, I recieve the error "Must call a designated initializer"
It's compile time error where super class designated initialisation not implement.
As subclass of UIButton must implement(override) init(frame:CGRect)("Designated Initialiser for UIButton") like as below,
> override init(frame: CGRect) {
super.init(frame: frame)
rightAnswer = true
}

Custom UITextField, init with no arguments

I have a custom UITextField and I'm trying to declare it like:
let textField = StandardTextField() // pretty much like UITextField()
My custom text field looks like:
class StandardTextField: UITextField {
init(frame: CGRect, size: CGFloat) {
super.init(frame: frame)
// some initialization
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
This is giving me an error because TextField has no initializer with no arguments. I tried adding:
init() {
super.init()
}
But this isn't possible since super.init() isn't the designated initializer. How can I achieve this?
You are probably looking for a convenience initialiser. But you will need to define a default frame size for your UITextField. Try like this:
class StandardTextField: UITextField {
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
// some initialisation for init with no arguments
}
override init(frame: CGRect) {
super.init(frame: frame)
// some initialisation for init with frame
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Well unless I'm blatantly missing something, you've declared your class as class Textfield: UITextField and based on how you're trying to create your object, your class should be defined as class StandardField: UITextField and the class doesn't need any initializers as Leo said.
class StandardTextField: UITextField {
//no initializers
}

Overriding convenience init

Trying to subclass an NSTextView:
class MYTextView : NSTextView {
init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setup()
}
}
I get this error: Must call a designated initializer of the superclass 'NSTextView' on this line: super.init(frame: frameRect).
According to the docs Convenience initializers must call another initializer available in the same class.. See 'Initializer Chaining' below:
https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_286
But for NSTextViews the only designated inits i get are super.init(frame:, textContainer:) & super.init(coder: coder) & super.inti(). init(frame:) does some setup which I'd rather not implement myself.
Is there some way to use a super class's convenience initializer?
Override the designated initialisers:
class MyTextView : NSTextView {
init(frame frameRect: NSRect, textContainer aTextContainer: NSTextContainer!) {
super.init(frame: frameRect, textContainer: aTextContainer)
setup();
}
func setup() {
...
}
}
var textView = MyTextView(frame: NSRect())
Since all the designated initialisers are overridden, all convenience will be automatically inherited.
There are two other designated initialzers to override:
init() {
}
and
init(coder:) {
}
I was just tripped up by this exact problem as well. I still Swift initialization with inheritance tricky, so definitely possible I'm not understanding something else here.
The accepted answer seems to suggest overriding init(frame: textContainer:), init, and init(coder:) will make init(frame:) accessible, but is does not for me.
The only way I was able to get things working as I wanted was with this:
override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
super.init(frame: frameRect, textContainer: container)
setup()
}
override init(frame frameRect: NSRect) {
// this will end up calling init(frame:textContainer:)
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}