2

I am working on an application where I need to be able to change the same .property for multiple UI objects to similar values at the same time.

As a SO-friendly example to illustrate my question, suppose you have 3 UILabels named label1, label2, and label3 that are being imported from an external .xib file, so they're each declared as @IBOutlet at the top of the VC. When you set up the labels and drag the outlets to the VC, XCode helpfully adds the declarations to the code for you, including the !-bang to implicitly unwrap the UILabel conditional.

@IBOutlet weak var label1: UILabel!
@IBOutlet weak var label2: UILabel!
@IBOutlet weak var label3: UILabel!

Now suppose you want to change the .text property of each to a new value called newValue:

let newValue = "This is Label #"

label1.text = newValue + "1"
label2.text = newValue + "2"
label3.text = newValue + "3"

returns:

This is Label #1
This is Label #2
This is Label #3

Straightforward enough, but not very DRY. In production I will have much longer groups of labels to update, so I'd like to simplify this.

Here's where it gets weird. Try expressing the same 3 statements in a for-in loop:

let labelArray = [label1, label2, label3]
for i in 0...2 {
labelArray[i].text = newValue + "\(i+1)"

The above code will throw a compiler error:

--> Value of optional type 'UILabel?' must be unwrapped 
to refer to member 'text' of unwrapped base type 'UILabel'

I can certainly fix the code to satisfy the compiler -- that's not my question. For one, adding a ! to the labelArray[i] statement will dismiss the error:

labelArray[i]!.text = newValue + "\(i+1)"

But the question is -- why is this necessary? One, aren't the UILabel conditionals already implicitly unwrapped in the @IBOutlet statement, and two, why does setting the .text property one at a time, each in a separate statement, not throw the same compile error? What's different?

Full code:

import UIKit

class ViewController: UIViewController{

    // Outlet Declarations for UILabels
    @IBOutlet weak var label1: UILabel!
    @IBOutlet weak var label2: UILabel!
    @IBOutlet weak var label3: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        let newValue = "This is Label #"

        // This method doesn't throw an unwrap error
        label1.text = newValue + "1"
        label2.text = newValue + "2"
        label3.text = newValue + "3"

        // This method throws an unwrap error
        let labelArray = [label1, label2, label3]
        for i in 0...2 {
            labelArray[i].text = newValue + "\(i+1)"
        }

    }
}
MMac
  • 483
  • 1
  • 4
  • 13
  • `for i in 0...2 { labelArray[i]` this code is begging for a bug. Try instead `for (i, label) in [label1, label2, label3].enumerated()` – Alexander Jan 05 '19 at 17:45
  • Create an IBOUlet collection like `@IBOutlet weak var allLbls: [UILabel]!` and link all labels in a specific order to it , then your problem of ? will be fixed – Shehata Gamal Jan 05 '19 at 17:47
  • @Alexander -- This is sample code I whipped up to demonstrate what I'm seeing in my larger project. But it's absolutely a fair point and I appreciate you mentioning it. This kind of DRY-ing out is still new to me so I'll take a look at how to use .enumerated in this context. Thanks! – MMac Jan 05 '19 at 17:49
  • 1
    @MMac It's not about repetition, it's about hard coding bounds that are sure to get out of sync with the collection they iterate – Alexander Jan 05 '19 at 17:51
  • @Sh_Khan Woah, that's a thing?! – Alexander Jan 05 '19 at 17:52
  • @Sh_Khan -- thanks for the code suggestions! My question, though is why this behavior happens in what otherwise appears to be two ways of saying the same thing. From the question cited by vadian (which I'm not sure I'd characterize as duplicate, but YMMV...), it sounds like it's a quirk in the way the compiler interprets UILabel? vs UILabel! as an explicit type. E.g.: https://stackoverflow.com/a/48081595/10826648 – MMac Jan 05 '19 at 17:52
  • 1
    @Alexander -- OK, now I've had a chance to play with .enumerated(), and while I didn't really understand the significance at first, now I *totally* get it: that using for i in 0...X notation is hardcoding the # of iterations the loop will do, which works but presents a point of failure if the size of the array changes. And I TOTALLY GET now why .enumeration() is a way better way of dealing with this issue -- basically saying "count how many items are in the array first, then iterate based on that). Thx for the tip -- I have worked that into my code. – MMac Jan 06 '19 at 03:09
  • @MMac :) this makes me super happy to hear. – Alexander Jan 06 '19 at 04:18

0 Answers0