suclassing a UIButton of type .system - ios

I am trying to subclass UIButton, but i want it to be of type .system. I am struggling with initializers
class FormButton: UIButton {
var type FormButtonType: FormButtomType.oneSelection
init(oftype formType: FormButtomType) {
self.type = formType
super.init(type: .system)
}
}
problem is that I have the following error message : "Must call a designated initializer of the superclass 'UIButton'

You can't override a convenience method and call super convenience method...
As an alternative you can do a static method that return a FormButton of UIButtonType.system type.
class FormButton: UIButton {
class func newButton() -> FormButton {
return FormButton.init(type: .system)
}
}
Use it like this
let button = FormButton.newButton()

I'm guessing that you need the System type because of the highlighted animation behaviour.
If so, You can override the Highlighted property in your UIButton subclass like so:
var isHighlighted: Bool {
didSet {
// If you have images in your button, add them here same as the code below
(self.isHighlighted && self.titleLabel != nil) ? (self.titleLabel!.alpha = 0.2) : (self.titleLabel!.alpha = 1.0)
}
}

The error is little bit confusing
Must call a designated initializer of the superclass 'UIButton'
but what it is trying to say is that you need to call designated initialiser before using self. So shift self.type call after super.init call. You have created a convenience initialiser which doesn't require call to super, you need to call self.
First of all, this line is syntactically wrong.
var type FormButtonType: FormButtomType.oneSelection
should be be
var type: FormButtonType = FormButtomType.oneSelection
Now you can subclass it easily.
import UIKit
class FormButton: UIButton {
var type: FormButtonType = FormButtomType.oneSelection
// convenence initializer, simply call self if you have
// initialized type
convenience init(type: UIButtonType) {
self.init(type: type)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now if you want to initialise some property which doesn't have default or optional value then you will need to override the designated initialiser.
For Example,
class FormButton: UIButton {
var type: UIButtonType = UIButtonType.system
var hello: String // property doesn't have initial value
// This is designated initialiser
override init(frame: CGRect) {
self.hello = "Hello"
super.init(frame: .zero)
}
convenience init(type: UIButtonType) {
self.init(type: .system)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You need to the call superclass' designated initializer:
init(oftype formType: FormButtomType) {
super.init(frame: yourframe)
self.type = formType
}

Related

Subclass of UIButton cant call the buttonType initializer

I am trying to subclass a UIButton so that its default initilizer sets it up the same way it would if you called UIButton(type: UIButtonType.roundedRect). However, I am unable to due to some Swift restrictions saying that that is not a designated initilizer. Additionally the buttonType property is read only.
How can I do this in Swift?
For reference this code does not compile because I do not call a designated initializer.
class ToggleButton : UIButton {
init() {
super.init(type: UIButtonType.roundedRect)
}
required init?(coder aDecoder: NSCoder) {
fatalError("TROUBLE")
}
}
Swift can be a little annoying with the initializers. This should do it for you.
class ToggleButton : UIButton {
convenience init() {
self.init(type: .roundedRect)
}
convenience init(type buttonType: UIButtonType) {
self.init(type: buttonType)
}
required init?(coder aDecoder: NSCoder) {
fatalError("TROUBLE")
}
}
EDIT: Feb 12, 21
As of Swift 5, this can be accomplished with the following.
class ToggleButton: UIButton {
convenience init() {
self.init(type: .roundedRect)
}
}

How to properly init passed property in subclass of UIView?

I have a subclass of UIView that I would like to pass a property to. As much as I've tried, I don't truly understand all elements of initializing.
Here is a simplified version of my code:
class inputWithIncrementView : UIView, UITextFieldDelegate {
var inputName : String // This is the property I want to receive and init
override init (frame : CGRect) {
super.init(frame : frame)
// [this is where i will use the inputName property passed on initialization]
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// [other functions and stuff working fine here]
}
I have tried a number of things, but I'm getting confused between the UIView initializer and the way I normally initialize a non-subclassed class.
How do I modify this code to receive the string property, initialize it? Thanks
If you want to initialize a UIView with a custom property you must reconfigure its initializer:
class InputWithIncrementView: UIView {
let inputName: String
init(inputName: String) {
self.inputName = inputName
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
}

Why can't I use "self.init(type: .custom)" in convenience initializer at my subclass of UIButton

I know must call a designated initializer of the superclass, I think init(type: UIButtonType) had called a designated initializer, so I used it in subclass convenience initializer, but failed
class TSContourButton: UIButton {
enum ContourButtonSizeType {
case large
case small
}
convenience init(type:ContourButtonSizeType) {
self.init(type: .custom)
}
then, I tried this. It compiles okay. but, It doesn't look professional
class TSClass: UIButton {
convenience init(frame: CGRect, myString: String) {
self.init(frame: frame)
self.init(type: .custom)
}
so, I doubt that I may think wrong. So, I did some test. It successfully called super convenience initializer. Why I can't use self.init(type: .custom) in convenience initializer at my subclass of UIButton?
class person: UIButton {
var name: String = "test"
override init(frame: CGRect) {
super.init(frame: .zero)
self.name = "one"
}
convenience init(myName: String) {
self.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class man: person {
convenience init(mySex: Int) { // it successfully call superclass convenience initializer
self.init(myName: "info")
}
If, for say, name is your mandatory field, you implement all your initial set up in a function. And you should handle if name is not available. I'll keep small as the default option if no type was provided.
// MARK:- Designated Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup(type: .small)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialSetup(type: .small)
}
// MARK:- Convenience Initializers
convenience init(type: ContourButtonSizeType) {
self.init(frame: .zero)
initialSetup(type: type)
}
func initialSetup(type: ContourButtonSizeType) {
// handle all initial setup
}
init(type: UIButtonType) is not UIButton's designated initializer, init(frame: CGRect) is the right designated initializer of UIButton
you just need to overwrite init(frame: CGRect)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
if you want to customize your own button, you can add convenience init(myType: UIButton.ButtonType)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
let button2 = MyButton(myType: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
// 创建自己的遍历构造器
// this is what you want
convenience init(myType: UIButton.ButtonType) {
self.init(type: myType) // called the convenience initializer which you automatically inherit from super class
// customize your own button
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If this is useful to you, can you give me a like, (づ ̄ 3 ̄)づ

Initialize UIButton with parameters

I want to initialize a subclass of UIButton with parameters, however I am getting a EXC_BAD_INSTRUCTION error in my init(create: ; coder:) method:
class DayButton: UIButton {
var forCreateView: Bool
init(create: Bool? = false, coder: NSCoder? = nil){
self.forCreateView = create!
super.init(coder: coder!) //EXC_BAD_INSTRUCTION
}
convenience required init(coder aDecoder: NSCoder) {
self.init(create: Bool(), coder: aDecoder) //most definitely not right
}
}
In Swift its "better" to try to bypass custom subclass initiation as good as possible <- my opinion don't listen to it... :-)
For guys that anyway love it... You could create a convenience initializer, init your stuff here and call a designated initializer, to init the superclass also... below is an example...
class DayButton: UIButton {
var forCreateView: Bool = false
convenience init (forCreateView: Bool) {
self.init()
self.forCreateView = forCreateView
}
}
let myButton: UIButton = DayButton(forCreateView: false) //Usage

How to implement two inits with same content without code duplication in Swift?

Assume a class that is derived from UIView as follows:
class MyView: UIView {
var myImageView: UIImageView
init(frame: CGRect) {
super.init(frame: frame)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
...
If I wanted to have the same code in both of the initializers, like
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
and NOT duplicate that code twice in the class implementation, how would I structure the init methods?
Tried approaches:
Created a method func commonInit() that is called after super.init -> Swift compiler gives an error about an uninitialized variable myImageView before calling super.init
Calling func commonInit() before super.init fails self-evidently with a compiler error "'self' used before super.init call"
What we need is a common place to put our initialization code before calling any superclass's initializers, so what I currently using, shown in a code below. (It also cover the case of interdependence among defaults and keep them constant.)
import UIKit
class MyView: UIView {
let value1: Int
let value2: Int
enum InitMethod {
case coder(NSCoder)
case frame(CGRect)
}
override convenience init(frame: CGRect) {
self.init(.frame(frame))!
}
required convenience init?(coder aDecoder: NSCoder) {
self.init(.coder(aDecoder))
}
private init?(_ initMethod: InitMethod) {
value1 = 1
value2 = value1 * 2 //interdependence among defaults
switch initMethod {
case let .coder(coder): super.init(coder: coder)
case let .frame(frame): super.init(frame: frame)
}
}
}
I just had the same problem.
As GoZoner said, marking your variables as optional will work. It's not a very elegant way because you then have to unwrap the value each time you want to access it.
I will file an enhancement request with Apple, maybe we could get something like a "beforeInit" method that is called before every init where we can assign the variables so we don't have to use optional vars.
Until then, I will just put all assignments into a commonInit method which is called from the dedicated initialisers. E.g.:
class GradientView: UIView {
var gradientLayer: CAGradientLayer? // marked as optional, so it does not have to be assigned before super.init
func commonInit() {
gradientLayer = CAGradientLayer()
gradientLayer!.frame = self.bounds
// more setup
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer!.frame = self.bounds // unwrap explicitly because the var is marked optional
}
}
Thanks to David I had a look at the book again and I found something which might be helpful for our deduplication efforts without having to use the optional variable hack. One can use a closure to initialize a variable.
Setting a Default Property Value with a Closure or Function
If a stored property’s default value requires some customization or setup, you can use a closure or global function to provide a customized default value for that property. Whenever a new instance of the type that the property belongs to is initialized, the closure or function is called, and its return value is assigned as the property’s default value. These kinds of closures or functions typically create a temporary value of the same type as the property, tailor that value to represent the desired initial state, and then return that temporary value to be used as the property’s default value.
Here’s a skeleton outline of how a closure can be used to provide a default property value:
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
Note that the closure’s end curly brace is followed by an empty pair of parentheses. This tells Swift to execute the closure immediately. If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.
NOTE
If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/de/jEUH0.l
This is the way I will use from now on, because it does not circumvent the useful feature of not allowing nil on variables. For my example it'll look like this:
class GradientView: UIView {
var gradientLayer: CAGradientLayer = {
return CAGradientLayer()
}()
func commonInit() {
gradientLayer.frame = self.bounds
/* more setup */
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
}
How about this?
public class MyView : UIView
{
var myImageView: UIImageView = UIImageView()
private func setup()
{
myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
override public init(frame: CGRect)
{
super.init(frame: frame)
setup()
}
required public init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
setup()
}
}
Does it necessarily have to come before? I think this is one of the things implicitly unwrapped optionals can be used for:
class MyView: UIView {
var myImageView: UIImageView!
init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
self.commonInit()
}
func commonInit() {
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
...
}
Implicitly unwrapped optionals allow you skip variable assignment before you call super. However, you can still access them like normal variables:
var image: UIImageView = self.myImageView // no error
Yet another option using a static method (added 'otherView' to highlight scalability)
class MyView: UIView {
var myImageView: UIImageView
var otherView: UIView
override init(frame: CGRect) {
(myImageView,otherView) = MyView.commonInit()
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
(myImageView, otherView) = MyView.commonInit()
super.init(coder: aDecoder)!
}
private static func commonInit() -> (UIImageView, UIView) {
//do whatever initialization stuff is required here
let someImageView = UIImageView(frame: CGRectZero)
someImageView.contentMode = UIViewContentMode.ScaleAspectFill
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
return (someImageView, someView)
}
}
Assign myImageView in both the init() methods based on a single image create function. As such:
self.myImageView = self.createMyImageView ();
For example, like such:
class Bar : Foo {
var x : Int?
func createX () -> Int { return 1 }
init () {
super.init ()
self.x = self.createX ()
}
}
Note the 'optional' use at Int?
Additionally, if the intention is to assign myImageView exactly once, it should be a let rather than a var. That rules out some solutions that only work for var.
Another complication is multiple instance variables with dependencies between them. This rules out inline initializers calling static methods.
These requirements can be addressed by overriding with convenience initializers, which delegate to a single designated initializer:
import UIKit
class MyView: UIView {
let myImageView: UIImageView
// Just to illustrate dependencies...
let myContainerView: UIView
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)!
}
required convenience init?(coder aDecoder: NSCoder) {
// Dummy value for `frame`
self.init(frame: CGRect(), coder: aDecoder)
}
#objc private init?(frame: CGRect, coder aDecoder: NSCoder?) {
// All `let`s must be assigned before
// calling `super.init`...
myImageView = UIImageView(frame: CGRect.zero)
myImageView.contentMode = .scaleAspectFill
// Just to illustrate dependencies...
myContainerView = UIView()
myContainerView.addSubview(myImageView)
if let aDecoderNonNil = aDecoder {
super.init(coder: aDecoderNonNil)
} else {
super.init(frame: frame)
}
// After calling `super.init`, can safely reference
// `self` for more common setup...
self.someMethod()
}
...
}
This is based on ylin0x81's answer, which I really like but doesn't work now (build with Xcode 10.2), as load from nib crashes with:
This coder requires that replaced objects be returned from initWithCoder:
This issue is covered on a separate question, with iuriimoz's answer suggesting to add #objc to the designated initializer. That entailed avoiding the Swift-only enum used by ylin0x81.

Resources