How to send argument when init UIView - swift

Its been asked before but without a coherent simple answer.
Usually you do :
override init (frame : CGRect)
{
super.init(frame : frame)
I need to do (which I can't because there is no such function to override) :
override init (frame : CGRect, number:Int)
{
super.init(frame : frame)
Which obviously gives a few errors.
Is there a way to create instances as usual and still pass arguments ?

To fulfill all rules and requirements you have to write
class MyView : UIView {
var number : Int
override convenience init(frame: CGRect)
{
self.init(frame:frame, number: 0)
}
init(frame: CGRect, number: Int)
{
self.number = number
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
number = 0
super.init(coder : aDecoder)
}
}
It might be easier to initialize the view the usual / designated way and set the property in an extra line.

There's no override method for the one that you specified, just write it like this
init(frame : CGRect, number: Int) {
super.init(frame : frame)
}

Related

Two classes to use the same function to create custom view - Swift

I am kind of new to Swift and I can't figure this out. I have two classes where I need to use the same function to set up a custom UIStackVIew (Rating Control that shows rating stars). Each class has a variable called value that needs to be passed inside the function. I don't want to be duplicating the same code of the setUpStackView function inside each class. I have the following code:
class Class1: UIStackView {
var variable1 = "value1"
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
setUpStackView(value: variable1)
}
class Class2: UIStackView {
var variable2 = "value2"
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
setUpStackView(value: variable2)
}
extension Class1 {
func setUpStackView(value: String){
//code to set UIStackView rating control and to use the variable value
}
}
How can I implement the extension for the Class2? I am stuck with this. Any help would be greatly appreciated!
One solution can be moved common code to protocol where you can abstract out:
protocol BaseStackView {
var variable :String { get set }
}
class Class1: UIStackView,BaseStackView {
var variable = "value1"
override init(frame: CGRect){
super.init(frame: frame)
self.setUpStackView(value: variable)
}
required init(coder: NSCoder) {
super.init(coder: coder)
self.setUpStackView(value: variable)
}
}
class Class2: UIStackView,BaseStackView {
var variable = "value2"
override init(frame: CGRect){
super.init(frame: frame)
self.setUpStackView(value: variable)
}
required init(coder: NSCoder) {
super.init(coder: coder)
self.setUpStackView(value: variable)
}
}
extension UIStackView {
func setUpStackView(value: String) {
//Your setup here
}
}
You have many options.
You can make Class2 inherit from Class1:
class Class2: Class1 {
var value = "value2" //You have access to Class1's value, so you can change it here
setUpStackView(value: value) //But there's a problem here
}
But you can't just call a function when you're in the middle of a class declaration.
But you can do this in the initializer:
class Class2: Class1 {
override init(frame: CGRect) {
super.init(frame: frame)
value = "value2"
setUpStackView(value: value)
}
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
value = "value2"
setUpStackView(value: value)
}
}
The above is kind of jank because you're referencing class properties within its own initializer.
Here's another solution:
You can make your setup function a function extension of UIStackView:
extension UIStackView {
func setUpStackView(value: String) {
//Your setup here
self.someProperty = value //self is referring to the stackview itself
}
}
Another option is to create a static function.
extension Class1 {
static func setUpStackView(stackVw: UIStackView, value: String) {
stackVw.someProperty = value
//Doing it like this still makes this function "belong" to Class1
//It also makes it so anyone can set up their stack view like
//this because they have to pass their stack view in here
}
}
Usage would be like this regardless of what class you're in and what inheritance hierarchy you have:
override init(frame: CGRect) {
super.init(frame: frame)
Class1.setUpStackVw(stackVw: self, value: self.variable)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Class1.setUpStackVw(stackVw: self, value: self.variable)
}
Yet another option is to create a protocol from which both Class1 and Class2 conform to.
protocol StackVwWithSpecialSetUp where Self: UIStackView {
var value: String {get}
}
extension StackVwWithSpecialSetUp {
func setUpStackView()
{
self.someProperty = self.value
}
}
And then you'd have your class conform to it
class Class1Or2: UIStackView, StackVwWithSpecialSetUp {
var value: String = "blah" //Compiler will force you to implement this
override init(frame: CGRect) {
super.init(frame: frame)
//Now you can use this
setUpStackVw()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You can use this here too
setUpStackVw()
}
}
Perhaps the way I'd do it is to just eliminate the need for the value property altogether:
class Class1: UIStackView {
override init(frame: CGRect) {
super.init(frame: frame)
setUpStackVw()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpStackVw()
}
func setUpStackVw()
{
self.accessibilityHint = "value1"
}
}
//////////
class Class2: Class1 {
override func setUpStackVw()
{
self.someProperty = "value2"
}
}

What argument do I use when calling draw() function?

I want to draw a line programmatically in swift. I have working code for it (I think), but calling the function requires a CGRect argument. And I'm unsure what code to write there.
The draw() class and function looks like this:
class LineView : UIView {
override func draw(_ rect: CGRect) {
var aPath = UIBezierPath()
aPath.move(to: CGPoint(x:2, y:2))
aPath.addLine(to: CGPoint(x:6, y:6))
aPath.close()
}
}
Now calling it from the main ViewDidLoad it would look like this:
var line = LineView()
line.draw(MISSING ARGUMENT)
But I have no idea what argument I'm supposed to pass. Nothing of the CGRect is used in the function, so I'm not even sure of its purpose.
UPDATE
In main I create the object like this:
class ViewController: UIViewController {
#IBOutlet weak var gameBoard: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var line = LineView()
gameBoard.addSubview(line)
}
}
And my draw class looks like this:
import UIKit
class LineView : UIView {
override init(frame: CGRect) {
super.init(frame: frame) //super = DO REGULAR INIT STUFF FOR UIView
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
var aPath = UIBezierPath()
aPath.move(to: CGPoint(x:2, y:2))
aPath.addLine(to: CGPoint(x:6, y:6))
aPath.close()
UIColor.red.set()
aPath.stroke()
aPath.fill()
setNeedsDisplay()
}
}
Got it working with:
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Don't call draw(rect: yourself. Never do.
It's implicitly called once by the framework after the view was initialized.
If you want to redraw the view call setNeedsDisplay() on the view.

init UIView with arguments?

My UIView looks like this :
override init (frame : CGRect)
{
super.init(frame : frame)
}
I would like to add an argument such as :
override init (frame : CGRect, number:Int)
What is the right way to do it so I can init and send arg in one line ?
You would define your class something like this:
class MyView: UIView {
let number: Int
init(frame: CGRect, number: Int) {
self.number = number
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
// Need to initialize the number property here. Do so appropriately.
super.init(coder: aDecoder)
}
}
The use of the number property is just an example. Do what you actually need.
And you would create an instance like this:
let mv = MyView(frame: .zero, number: 42)
Obviously you would pass a useful frame.
You can do something like this:
class CustomView: UIView{
var number: Int
init(frame : CGRect, number: Int) {
self.number = number
print("Number = \(number)")
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then add it to the stack like so:
let viewToAdd = CustomView(frame: CGRect(x: 20, y: 72, width: 100 , height: 100), number: 1)
viewToAdd.backgroundColor = .red
self.view.addSubview(viewToAdd)

Generate Value for Class Property

I'm trying to subclass UILabel and automatically generate a value for one of the properties, so that I can initialize with some values, and have others randomly generated.
For example, here I'd want to be able to create a new MyLabel, pass in a value for labelString and and have the value for labelNumber generated randomly.
class MyLabel: UILabel {
var labelString : String!
var labelNumber : Int!
init(frame: CGRect, labelString: String) {
self.labelString = labelString
self.labelNumber = createNumber()
super.init(frame: frame)
}
func createNumber() -> Int {
return Int(arc4random())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I get the error "Use of 'self' in method call before super.init initializes self." Is there a way to fix this, or a better pattern to be using?
Many thanks for any help!
You can't call instance methods from init until everything has been initialized. One way to handle it is to make createNumber a static function which doesn't require the class to be initialized to be called:
class MyLabel: UILabel {
var labelString : String!
var labelNumber : Int!
init(frame: CGRect, labelString: String) {
self.labelString = labelString
self.labelNumber = MyLabel.createNumber()
super.init(frame: frame)
}
static func createNumber() -> Int {
return Int(arc4random())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Another way to handle it is to move the initialization of labelNumber after the call to super.init. This works because labelNumber is an Optional and thus is initially given the value of nil which meets the requirement that all properties of the subclass MyLabel are initialized before calling the init of the superclass.
class MyLabel: UILabel {
var labelString : String!
var labelNumber : Int!
init(frame: CGRect, labelString: String) {
self.labelString = labelString
super.init(frame: frame)
self.labelNumber = createNumber()
}
func createNumber() -> Int {
return Int(arc4random())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

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
}