correct use of property observers

I am getting Property 'self.item' not initialized at super.init call error below at 10 and 14. For example, when I use a string as a property observer and init that string I don't get any error. However here the reason I am using a property observer is I would like to set the value for item property in runtime and automatically get that to call setupView() and I have to use my custom Type and I cannot init before runtime. I included Term Struct in the snippet for some context.



class ChoiceView: UIView {

    var item: Term  {
        didSet {
            setupView()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}


protocol Term {
    var value: String { get }
}
extension Term  {}
struct Statement  {
    var terms: [Term] = []
   
    mutating func addItem(term: Term) {
        terms.append(term)
    }
}
struct Fraction: Term {
    let numerator: Statement
    let denominator: Statement
    let value: String
}
struct Exponent: Term {
    let base: String
    let power: Statement
    let value: String
}
struct Variable: Term {
    let value: String
}
struct Operator: Term {
    let value: String
}
struct Number: Term {
    let value: String
}



Am I better of just using a computed property? That doesn't sound right to me. Please see below:

var item: Term  {
        get {
            setupView()
        }
    }


What am I missing here?


EDIT: To clarify the question: since Term is protocol, I cannot simply do:


    var item: Term = Term() {
        didSet {
            setupView()
        }


The compiler tells me 'Term' cannot be constructed because it has no accessible initializers.


I could do something like this:

var item: Term = Script(name"") {
        didSet {
            setupView()
        }


and this indeed compiles but it's so ugly looking. Isn't there a better way to write this?


Thanks in advance.

The error message is accurate. You must initialize your own properties before you call super.init().

If the item property doesn't always have a value, it could be typed as an optional with an intial value of nil.


var item: Term? = nil  {
        didSet {
            setupView()
        }
    }

You'll just need to adjust the code which uses it to deal with optional value.



If item should always have a value after it's first set, and doesn't get accessed before that point, you could use an implicitly unwrapped optional Term! set to nil. Then you wouldn't need to change your other code, but it isn't as safe as a normal optional since if it does get accessed before it has the value set the automatic unwrap will crash your app.

Hi LCS,


Here's how I initialize objects that's related to item:

for (_, term) in EnumerateSequence(item.terms) {
           switch term {
            case is Fraction:
                let fractionView:FractionView = FractionView()
                fractionView.item = term as? Fraction
                print("fraction")
            case is Variable:
                print("variable")
                let termView:TermView = TermView()
                termView.item = term
            case is Operator:
                print("operator")
                let termView:TermView = TermView()
                termView.item = term
            case is Number:
                print("number")
                let termView:TermView = TermView()
                termView.item = term
            default:
                break
            }  
}


and this is my TermView class where I have my item:

import UIKit
class TermView: UIControl {

    var labels = [UILabel]()

    var item: Term? = nil {
        didSet {
            setupView()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
         setupView()
    }

    private func setupView() {
          print(item) //prints correct object
          print(item?.value) //crashes program  
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
              setupView()
    }
}


as you see, I am using the type Term instead of each struct (Variable, Operator, Number). When I try to access the value of print(item?.value) I am getting a crash. If I use their own type; Variable, Operator, Number then program doesn't crash, but I am not sure how to cast their actual Types to the Item property in TermView.


Thank you.

I would pass the term in TermView init. Then you don't need optional or implicitly unwrapped optional.

Hi jsslai,


I would love to do that. However, I am stil trying to wrap my head around initalization delegation in Swift.


When I do this:



    class TermView: UIControl {
      
        var labels = [UILabel]()
      
        var item: Term
      
        override init(frame: CGRect, item: Term) {
            super.init(frame: frame)
            self.item = item
            setupView()
        }
      
        private func setupView() {
            print(item) /
            print(item?.value) /
        }
      
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupView()
        }
    }


Compiler gives me 2 errors:

line 08: Property 'self.item' not initialized at super.init call.

line 19: Property 'self.item' not initialized at super.init call.


What's the correct way of passing term in the init?

Remove the override from the first init and initalize item: Term before calling super init. If you don't need initWithCoder you can write it:


required init?(coder aDecoder: NSCoder) {
   fatalError("init(coder:) has not been implemented")
}

Thanks jsslai.


I have figured there's one little wrinkle with this whole setup. When I instantiate my termview instance I have to also pass a frame which I didn't have to do previously:

let termView:TermView = TermView(frame: CGRectZero, item: term)


I am passing CGRectZero and it works fine but I wonder if I could just omit it with a convenience init in the TermView.

I figured that out:


convenience init(item: Term) {
        self.init(frame:CGRectZero, item: item)
    }


    init(frame: CGRect, item: Term) {
        self.item = item
        super.init(frame: frame)
        setupView()
    }

Or init(item: Term) can call super.init(frame: CGRect) directly.

Per docs it says:

  • Convenience initializers must always delegate across.
  • Designated initializers must always delegate up.


so calling super in convenience init may not be a good idea fwiw.

Yeah I meant designated initializer, not convenience.

correct use of property observers
 
 
Q