I have a class called badge that inherits from another class and I want the constructor method to tell the style of the notification.
but I'm having this error:
Property 'self.notificationStyle' not initialized at super.init call
DefaultTableViewCell class
final public class DefaultTableViewCell: UITableViewCell
enum NotificationStyle {
case numberedSquare, circle
}
var notificationStyle: NotificationStyle = .numberedSquare
my goal is whenever someone instantiates this Badge class, it will be necessary to inform the notificationStyle of it, being square or circular in this case.
how can i solve this?
Badge class
#objc public class Badge: NotifyLabel
var notificationStyle: DefaultTableViewCell.NotificationStyle
init(frame: CGRect, notificationStyle: DefaultTableViewCell.NotificationStyle) {
self.notificationStyle = notificationStyle
super.init(frame: frame)
setup(notificationStyle: notificationStyle)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup(notificationStyle: notificationStyle)
}
init(notificationStyle: DefaultTableViewCell.NotificationStyle) {
self.notificationStyle = notificationStyle
super.init(frame: .zero)
setup(notificationStyle: notificationStyle)
}
func configureBadgeNotificationStyle(notificationStyle: DefaultTableViewCell.NotificationStyle) {
textAlignment = NSTextAlignment.center
layer.borderWidth = 1
layer.borderColor = Color.bostonRed.cgColor
clipsToBounds = true
textStyle = .label
backgroundColor = Color.white
switch notificationStyle {
case .circle:
layer.cornerRadius = 8
default:
layer.cornerRadius = 2
}
}
private func setup(notificationStyle: DefaultTableViewCell.NotificationStyle) {
configureBadgeNotificationStyle(notificationStyle: notificationStyle)
configureAccessibility()
}
NotifyLabel class
public class NotifyLabel: UILabel
public init(textStyle: TextStyle) {
self.textStyle = textStyle
super.init(frame: .zero)
applyTextStyle()
}
public override init(frame: CGRect) {
super.init(frame: frame)
applyTextStyle()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyTextStyle()
}
You can fix this by adding a line to set notificationStyle to a default value in init?(coder aDecoder: NSCoder):
required init?(coder aDecoder: NSCoder) {
self.notificationStyle = .numberedSquare //<-- Here
super.init(coder: aDecoder)
setup(notificationStyle: notificationStyle)
}
You have to do this because in your declaration of notificationStyle, there's no default value and it must have a value before calling super.init. In your other initializers, you set it based on the incoming arguments.
This is an initializer that it sounds like you're not using anyway, but it is required with UIViews that we implement this required initializer.
Related
I tried to add a double value to a subclass of UIButton in Swift. I tried all kind of inits and get and set options, but I couldn’t get it to work.
So I started with this:
class CVSTButton : UIButton {
var cvstPosition: Double
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
}
Then I tried:
class CVSTButton : UIButton {
var cvstPosition: Double {
get {
return self.cvstPosition
}
set {
self.cvstPosition = newValue
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
}
What’s is wrong here?
With Swift 3, according to your needs, you may choose one of the seven following code snippets to solve your problem.
1. Create your UIButton subclass with a custom initializer
This solution allows you to create instances of your UIButton subclass with the appropriate value for your property. With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
required init(value: Int = 0) {
// set myValue before super.init is called
self.myValue = value
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(value: 0)
// let button = CustomButton() // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 0
}
}
2. Create your UIButton subclass with a convenience initializer
This solution allows you to create instances of your UIButton subclass with the appropriate value for your property. With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
convenience init(squareOf value: Int) {
self.init(value: value * value)
}
required init(value: Int = 0) {
// set myValue before super.init is called
self.myValue = value
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(squareOf: 10)
// let button = CustomButton(value: 100) // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 100
}
}
3. Create your UIButton subclass with init(frame: CGRect) initializer
With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
override init(frame: CGRect) {
// set myValue before super.init is called
self.myValue = 0
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(frame: .zero)
//let button = CustomButton() // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 0
}
}
4. Create your UIButton subclass with init?(coder aDecoder: NSCoder) initializer
With this solution, you can create instances of your UIButton subclass from Storyboard.
import UIKit
class CustomButton: UIButton {
var myValue: Int
required init?(coder aDecoder: NSCoder) {
// set myValue before super.init is called
self.myValue = 0
super.init(coder: aDecoder)
// set other operations after super.init, if required
backgroundColor = .red
}
}
Usage:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: CustomButton!
override func viewDidLoad() {
super.viewDidLoad()
print(button.myValue) // prints 0
}
}
5. Create your UIButton subclass with init(frame: CGRect) and init?(coder aDecoder: NSCoder) initializers
With this solution, you can create instances of your UIButton subclass programmatically or from Storyboard.
import UIKit
class CustomButton: UIButton {
var myValue: Int
override init(frame: CGRect) {
// set myValue before super.init is called
self.myValue = 0
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
// set myValue before super.init is called
self.myValue = 0
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
6. Create your UIButton subclass with a default property value for your property
As an alternative to the previous solutions, you can assign an initial value to your property outside of the initializers.
import UIKit
class CustomButton: UIButton {
var myValue: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
7. Create your UIButton subclass with your property having an optional type
If you don't want to / can't set a default value to your property when your button is created, you must set your property type as an optional.
import UIKit
class CustomButton: UIButton {
var myValue: Int? = nil
override init(frame: CGRect) {
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
You need two things there -- (1) cvstPosition needs an initial value, either in the declaration or in the init before you call super.init(). (2) That call to fatalError is put in so you don't forget to implement the initializer -- it’s basically an on-purpose crash. Delete!
Setting the initial value in the declaration, there isn’t any need for an init:
class CVSTButton : UIButton {
var cvstPosition: Double = 0
}
Or setting the initial value in the initializer:
class CVSTButton : UIButton {
var cvstPosition: Double
required init(coder aDecoder: NSCoder) {
cvstPosition = 0
super.init(coder: aDecoder)
}
}
Swift >= 2.2:
Since this version subclassing the UIButton, makes your button to have .custom type.
Swift 2:
convenience init(type buttonType: UIButtonType) {
super.init(frame: CGRectZero)
// this button be automatically .Custom
}
Swift:
override class func buttonWithType(buttonType: UIButtonType) -> AnyObject {
let button = super.buttonWithType(buttonType) as! UIButton
// your default code
return button
}
Note: I'm using Swift 3 in Xcode 8.3.3
This is a simple and easy workaround I've been using when needing to add custom properties and methods to a UIButton:
class CVSTButton: UIButton {
var cvstPosition: Double
static func button(withCVSTPosition cvstPosition: Double) -> CVSTButton {
let button = CVSTButton(type: .detailDisclosure) // You may adjust the initializer used to suit your needs.
button.cvstPosition = cvstPosition // Then you can simply set the the properties (which are passed as arguments to the factor/class method)
return button
}
}
To use it:
let cvstButton = CVSTButton.button(withCVSTPosition: 2.0)
I have extended the UIView class and added a property for cornerRadius. The property does set to desired value. I have made two custom classes one derives from UITextField and another from UILabel. UITextField gets rounded corners but UILabel does not.
Any help in this regard will be highly appreciated.
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue}
}
}
In your BLabel class you access the cornerRadius property of your UIView extension in the init method. This is before you have any chance to set a specific corner radius value so it will be 0.
There's no point to the line layer.cornerRadius = cornerRadius in the init method of BLabel. Simply create the BLabel instance and then set its cornerRadius property.
let label = BLabel(frame: someFrame)
label.cornerRadius = 5
Are you sure your UITextField is responding to cornerRadius? Or are you maybe just seeing the normal rounded corners?
Try changing your BLabel to this - it will make sure the initializations are being called properly:
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
public override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() {
// As noted by "rmaddy" ---
// setting .cornerRadius here does nothing, as it is always equal to Zero
// the UIView extension will handle it
//layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
// the following just makes it easy to confirm
// that this code is being executed
backgroundColor = UIColor.red
textColor = UIColor.yellow
textAlignment = .center
}
}
I would like to thank #rmaddy for help. I am writing this for the benefit for all. The code given by rmaddy works. But, after testing it I figured out that it is not required. Just setting layer.masksToBounds = true in UIView extension cornerRadius setter method does the trick. So the entire problem was solved by just this one line of code.
So the final code looks like this and it works:
#IBDesignable
public class BTextField: UITextField {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue
layer.masksToBounds = true}
}
}
I hope it helps others also.
I am new to Swift programming and I have a few textfields with similar properties. Can I create one textfield with defined properties and then extend them to other textfields. num inherits properties like borderColor and borderWidth from textfield1.
class TextElement: UITextField {
var textfiedl1: UITextField = UITextField(frame:CGRectMake(38,383,299,44))
textfield1.layer.borderColor = UIColor.grayColor().CGColor
textfield1.layer.borderWidth = 1.0
}
class TextElement2 : UITextField {
var num: TextElement = TextElement(frame: CGRectMake(38,416,299,44))
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
num.backgroundColor = UIColor.redColor()
self.addSubview(num)
}
This does not give any output. The simulator screen is blank. Any solution for this.
This is completely off the top of my head (not tested) but I think you want something along the lines of the following:
Base Class:
class BaseTextField: UITextField {
init() {
super.init(frame: CGRectZero)
self.layer.borderColor = UIColor.grayColor().CGColor
self.layer.borderWidth = 1.0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Sub Class:
class YourTextField: BaseTextField {
init(frame: CGRect, backgroundColor: UIColor) {
super.init()
self.frame = frame
self.backgroundColor = backgroundColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage in Viewcontroller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textField = YourTextField(frame: CGRectMake(38, 416, 299, 44), backgroundColor: UIColor.redColor())
self.view.addSubview(textField)
}
}
Note:
You probably want to actually do something in required init?(coder aDecoder: NSCoder) but for here I left the default implementation.
What you need here is a helper object. Lightweight structs are perfect for this. Create a struct embodying the configuration features. Give the struct a method that you can call, passing self as a parameter, allowing the struct to configure your text field (warning: this is Swift 3):
struct TFConfig {
let borderColor = UIColor.gray().cgColor
let borderWidth:CGFloat = 1.0
func configure(_ tf:UITextField) {
tf.layer.borderColor = borderColor
tf.layer.borderWidth = borderWidth
}
}
class MyTextField : UITextField {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
TFConfig().configure(self)
}
}
// ... similarly for other text fields
In Storyboard I have UITextField, I also created custom class:
class WLTextField: UITextField {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
layer.cornerRadius = 2
layer.masksToBounds = true
layer.borderColor = UIColor.grayColor().CGColor
layer.borderWidth = 1
}
}
Then attached this class to my UITextField:
But, there is no result for this in Storyboard. How can I achieve this? I need to get such effect like it is with #IBDesignables. But since I know what I need to set up for every text field, I do not need set a value and then update this within didSet.
Do I need to set at least one value to make changes live in Storyboard on some UIView?
In order for a component to render in the Interface Builder you must flag the class as #IBDesignable and you must implement the initialise init(frame: CGRect) -
#IBDesignable class WLTextField: UITextField {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayer()
}
required override init(frame: CGRect) {
super.init(frame: frame)
setupLayer()
}
func setupLayer () {
layer.cornerRadius = 2
layer.masksToBounds = true
layer.borderColor = UIColor.grayColor().CGColor
layer.borderWidth = 1
}
}
Utilizing constants in subclasses with lot of initializers to override is tedious. Look at the class below, I need to duplicate the code in both initializers.
class Test : UIView {
let subview: UIView
override init(frame: CGRect) {
subview = UIView() // once
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
subview = UIView() // twice
super.init(coder: aDecoder)
}
}
If I try to make use of a common initializer then I get the following errors (see the comments)
override init(frame: CGRect) {
commonInit() // 1: Use of 'self' in method call 'commonInit' before super.init initializes self
super.init(frame: frame) // 2: Property 'self.subview' is not in initialized at super.init call
}
private func commonInit() {
subview = UIView() // 3: Cannot assign to 'subview' in 'self'
}
It works fine if I do not use a constant and define the subview like:
var subview: UIView?
And then of course switch order in init like this:
super.init(frame: frame)
commonInit()
So my question: is there no way to use a common initializer for constants in Swift as of now?
EDIT: I totally forgot to mention that the struggle here is that I can't initiate the subview before I'm in the init, it's initiated based on data that is not known when declaring the constant.
Try this:
class Test : UIView {
let subview = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Another option:
class Test : UIView {
let subview:UIView
init(frame: CGRect?, coder: NSCoder?) {
// The first phase initialization here
subview = UIView()
if let frame = frame {
super.init(frame: frame)
}
else if let coder = coder {
super.init(coder: coder)
}
else {
super.init()
}
// the Second phase initialization here
self.addSubview(subview)
}
convenience init() {
self.init(frame: nil, coder: nil)
}
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)
}
required convenience init(coder aDecoder: NSCoder) {
self.init(frame: nil, coder: aDecoder)
}
}
A little bit cleaner alternative:
class Test : UIView {
let subview:UIView
private enum SuperInitArg {
case Frame(CGRect), Coder(NSCoder), None
}
private init(_ arg: SuperInitArg) {
subview = UIView()
switch arg {
case .Frame(let frame): super.init(frame:frame)
case .Coder(let coder): super.init(coder:coder)
case .None: super.init()
}
addSubview(subview)
}
convenience init() {
self.init(.None)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))
}
required convenience init(coder aDecoder: NSCoder) {
self.init(.Coder(aDecoder))
}
}
Do the following:
class Test : UIView {
let subview = UIView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//edit subview properties as needed
}
}
This works well for me:
// Declare this somewhere (it can be used by multiple classes)
class FrameCoder: NSCoder {
let frame: CGRect
init(_ frame: CGRect) {
self.frame = frame
super.init()
}
}
Then, when you want a common initializer pattern, use this:
class MyView: UIView {
let something: SomeType
required init(coder aDecoder: NSCoder) {
if (aDecoder is FrameCoder) {
super.init(frame: (aDecoder as! FrameCoder).frame)
}
else {
super.init(coder: aDecoder)
}
// Common initializer code goes here...
something = // some value
}
override init(frame: CGRect) {
self.init(coder: FrameCoder(frame))
}
}
The advantage of using this method is you don't need to create default values for let definitions -- you can set them to the correct values in context, just like you would if there were only a single initializer.
Note that you can use this technique for initializers taking arbitrary values (not just for init(frame: CGRect)) -- you can create a specific NSCoder subclass to wrap any value and type you need to pass to an initializer, and then chain it into your init(coder:) method.
(also, there's probably some way to do this with a generic... haven't quite figured that out yet! Anyone...?)
One option is following the Xcode pattern:
class Test : UIView {
var subview: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
subview = UIView()
}
}
Notice your subview is a var
Another option is:
class Test : UIView {
let subview: UIView = {
let sv = UIView()
// some config, (i.e.: bgColor etc., frame is not yet _real_
// can't yet access instance's frame and other properties
return sv
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
// frame might be valid, preferably use layout constraints
addSubview(subview)
}
}
Hope this helps