Search code examples
iosiphoneswiftnulliboutlet

iOS custom view - very weird properties behavior


Using Swift 2.0

I'm trying to use a custom view with a label (not just that) to show some information on the screen, so my idea was to programatically add the custom view to a scrollview on the ViewController, and set the label's text (to start with)

Problem: I just can't get a moment where both the label and the string i want to set the label's text to are not null, and for some reason the string gets null in the end.

So i made a extensive debugging to show what's the state for each var in each moment, and that made me even more confused: (this is Xcode's log output)

my init was called, fatura String value: Optional("random text")
init with coder was called

 - On awake from Nib
Label outlet is NOT null
fatura String is null
view loaded from Nib was added

 - On Setup
Label outlet is null
fatura String is NOT null
checking fatura String value from ViewController: Optional("random text")

 - On ViewController's viewDidLoad
Label outlet is null
fatura String is NOT null

 - On ViewController's viewWillAppear
Label outlet is null
fatura String is NOT null

 - On Button touched action
Label outlet is NOT null
fatura String is null

I'm new to iOS, coming from some years in android development,and I would like to really understand what's going on, so any good articles and books would be welcome as well. I've found a lot of stuff on custom views, but they all are somewhat different , so I don't know which one to use, and they're mostly implementations only, no explanation whatsoever.

Questions:

  • Why does the vencimentoLb Label's outlet starts out as "not null", before the Nib view was loaded?

  • Why is the fatura String null, after* my init was called, which sets it out? *at least that's what the log tells me

  • Why the hell is my fatura String null on the buttonClicked action??

To Summarize: how can i get both the label and the string not null, so to set the text on the label?

I know it's a lot of questions, so an answer for any of them is much appreciated

"Solution" found:

call customview.view to force the outlets to get instanciated. that did not work (the customview has no view member apart from the one I created, and calling that one made no difference)

Custom View's code:

class FaturaCard: UIView {

    var view:UIView!
    @IBOutlet var vencimentoLb: UILabel!
    @IBOutlet var cardView: UIView!

    var fatura:String?

    init(string:String , frame:CGRect){
        fatura = string
        print("my init was called, fatura String value:",self.fatura)
        super.init(frame: frame)
        setup()
    }

    override init(frame: CGRect){
        super.init(frame:frame)
        print("init with frame was called")
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        print("init with coder was called")
    }

    @IBAction func onBotaoClicked(sender: AnyObject) {
        print("\n - On Button touched action")
        checksWhatsNull()
    }

    func setup(){
        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        addSubview(view)
        print("view loaded from Nib was added")
        print("\n - On Setup")
        checksWhatsNull()
    }

    func checksWhatsNull(){
        if vencimentoLb == nil{
            print("Label outlet is null")
        }else{
            print("Label outlet is NOT null")
        }

        if self.fatura == nil{
            print("fatura String is null")
        }else{
            print("fatura String is NOT null")
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        print("\n - On awake from Nib")
        checksWhatsNull()
    }

    func loadViewFromNib() -> UIView{
        let nib = UINib(nibName: "FaturaCard", bundle: nil)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        return view
    }

}

Edit:

Custom view instantiation code:

var view2:FaturaCard!
override func viewDidLoad() {
        super.viewDidLoad()

        view2 = FaturaCard(string:"random text",frame: CGRect(x: 00, y:145, width: scrollView.bounds.size.width, height: 145))

        print("checking fatura String value from ViewController:",view2.fatura)

        self.scrollView.addSubview(view2)

        print("\n - On ViewController's viewDidLoad")
        view2.checksWhatsNull()

        self.scrollView.contentSize.height=145*2
    }

Solution

  • You have two outputs from init statements in the log in your question.

    This means that you are loading two instances of the custom view.

    The logs you are seeing are from different instances, depending on the action - values aren't changing, you are seeing the values from different instances.

    Adding print(self) to your logging should hopefully make this clear.

    init(coder:) is going to be when you create a view from a nib or storyboard. You are creating the view with a custom init (which you see in the logs), then creating a second instance of the view in the setUp method, and holding it within the original. This isn't right.

    I think you've read about accessing view to force a view to be loaded, and tried to do that here - this applies to view controllers, not views, and does not apply at all to your case.

    You should probably be creating the view directly from the nib, then setting the fatura property - it isn't possible to tell from what's in the question, but I assume your outlets are pointing to the main view itself rather than to file's owner, so you can create with an owner of nil.

    Looking at your updated code, you probably want something like this.

    Instead of this line in viewDidLoad:

    view2 = FaturaCard(string:"random text",frame: CGRect(x: 00, y:145, width: scrollView.bounds.size.width, height: 145))
    

    Use this:

    let nib = UINib(nibName: "FaturaCard", bundle: nil)
    view2 = nib.instantiateWithOwner(nil, options: nil)[0] as! FaturaCard
    view2.frame = CGRect(x: 00, y:145, width: scrollView.bounds.size.width, height: 145)
    view2.fatura = "Random Text"
    

    It's awkward loading views from nibs, ideally you would put this stuff in a class method on FaturaCard where you can also pass in a frame and a string.