How to implement two inputs with the same content without duplicating code in Swift?

Assume a class derived from a 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 initializers, for example

 self.myImageView = UIImageView(frame: CGRectZero) self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill 

and DO NOT duplicate this code twice in the class implementation, how would I structure the init methods?

Tried approaches:

  • The created func commonInit() method, which is called after the super.init β†’ Swift compiler, throws an error about the uninitialized variable myImageView before calling super.init
  • A call to func commonInit() before super.init fails, obviously with the compiler error "self" used before calling super.init "
+45
ios swift
Jun 03 '14 at 19:29
source share
6 answers

I just had the same problem .

As GoZoner said, marking your variables as optional will work. This is not a very elegant way, because you need to expand the value every time you want to access it.

I will write a request for improvement with Apple, perhaps we could get something like the "beforeInit" method, which is called before each init, where we can assign variables, so we do not need to use optional vars.

Until then, I just put all the assignments in the commonInit method, which is called from the allocated initializers. For example:.

 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 looked again at the book, and I found something that could be useful for our deduplication efforts without using the optional variable hack. You can use closure to initialize a variable.

Setting default property value with closing or function

If the default value for stored properties requires some customization or customization, you can use the close function or the global function to provide a custom default value for this property. Whenever a new instance of the type to which the property belongs is initialized, either the function is closed and its return value is assigned as the default value of the property. These types of closures or functions usually create a temporary value of the same type as the property, spoil this value to represent the desired initial state, and then return this temporary value, which will be used as the default value of the property.

Here is a skeleton diagram of how you can use closure 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 closing closes with a curly brace followed by an empty pair of brackets. This means that Swift will immediately close. If you omit these parentheses, you are trying to assign this property to the closure, not the return value of the closure.

Note

If you use closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point at which closure is performed. This means that you cannot access any other property values ​​from your closure, even if these properties have default values. You also cannot use the implicit self property or call any of the instance methods.

Excerpt from: Apple Inc. "Fast programming language." interactive books. https://itun.es/de/jEUH0.l

This is how I will use from now on, because it does not bypass a useful function that does not allow nil for variables. For my example, it would 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() } } 
+19
Jun 05 '14 at 3:53
source share

We need the usual place to put our initialization code before calling the superclass initializers, so what I'm using right now is shown in the code below. (It also covers the case of interdependence between default values ​​and keeps 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) } } } 
+17
Nov 06 '14 at 5:05
source share

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() } } 
+3
Sep 19 '14 at 7:17
source share

Assign myImageView in both init() methods based on the same image creation function. In this way:

 self.myImageView = self.createMyImageView (); 

For example, for example:

 class Bar : Foo { var x : Int? func createX () -> Int { return 1 } init () { super.init () self.x = self.createX () } } 

Pay attention to optional use in Int?

+1
Jun 03 '14 at 19:39
source share

Is this necessary? I think this is one of the things that implicitly deployed options can be used to:

 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 expanded options let you skip assigning a variable before calling super . However, you can access them like regular variables:

var image: UIImageView = self.myImageView // no error

+1
Sep 14 '14 at 1:51 on
source share

Another option using the static method (the "otherView" option has been added 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) } } 
0
Jul 17 '17 at 12:37
source share



All Articles