Did you know that in Swift a computed property can be a tuple? I sure didn’t.

In my last post I described Using AppStorage with complex objects and SwiftUI Pickers. Soon after dealing with that I encountered a scenario where I learned that computed properties can be a tuple.

If you’re already a tuple expert use this link to jump past my tuple summary!

Tuple Basics

Tuples in Swift can be used in many ways. You can have a function that returns a tuple. Here is a function that returns a tuple that is two integers.

func TupleFan(firstStart : Int, secondStart: Int) -> (Int, Int) {
    return (Int.random(in: firstStart...Int.max),
            Int.random(in: secondStart...Int.max))
}

let result = TupleFan(firstStart: 0,
                      secondStart: Int.random(in: 0...Int.max))
result.0
result.1

I’m accessing the the resulting parts of the returned tuple individually (result.0 for the first parameter, for example).

You can use tuples in a switch statement.

let a = Int.random(in: 0...Int.max)
let b = Int.random(in: 0...Int.max)

switch (a, b) {
    case (0, 5...Int.max):
        print("some b")
    case (0...Int.max, 0):
        print("just a")
    case (007, 007):
        print("Bond \(a)")
    default:
        print("who knows?")
}

I even learned from Nick Lockwood (via Uli Kusterer) that tuple arguments can be labeled!

That makes our original function so much more understandable when parsing the results in my opinion.

func TupleFan(firstStart : Int, secondStart: Int) -> (firstResult: Int,
                                                      secondResult: Int) {
    let a = Int.random(in: firstStart...Int.max)
    let b = Int.random(in: secondStart...Int.max)
    return (firstResult: a, secondResult: b)
}

let result = TupleFan(firstStart: 0,
                      secondStart: Int.random(in: 0...Int.max))
result.firstResult
result.secondResult

Now that we’ve covered the basics of tuples, let’s discuss more of the the situation I encountered. If you’d rather not read some discussion about the AppStorage property wrapper you can use this link to continue to the tuple discussion.

Accessing data saved with AppStorage

As a reminder, in the last post I stored the selection’s name via AppStorage.

@AppStorage("my combo") private var theComboName : String = justNames.first!

Some people don’t realize this, but when you save a key/value pair with AppStorage you can still access that data via the older ways we had of accessing UserDefaults data. When I rewrote my app, Museum Shuffle, I used SwiftUI for 99% of the user interface parts. That meant getting to use AppStorage to save settings. However, I was able to retain a lot of the “guts” of the app that weren’t user facing. Since I was careful to use the existing key values when I used AppStorage I didn’t have to change the underlying code that was using the older methods of accessing UserDefaults data.

So since I used the key “my combo” to save my data with AppStorage in the example above I can use that exact same key to access the data like we do below. Note that I normally would store the “my combo” key as a let constant, but I like seeing the value for clarity with this simple example.

        let typeFound : String
        
        if UserDefaults.standard.value(forKey: "my combo") != nil,
           let typeFoundTmp = UserDefaults.standard.string(forKey: "my combo") {
            typeFound = typeFoundTmp
        } else {
            typeFound = "unknown"
        }

This came up when I was developing an enum.

Constructing an enum

I found myself developing an enum, where you cannot make use of AppStorage. Not a problem, because I was able to access the data using the older methods I described above. I created a computed property that “calculated” the resulting value based on data that was found via UserDefaults.

enum FancyPants : String {
    case frozen = "Frozen Pizza"
    case fresh = "Ooni Pizza"

    var computed : String {
        let typeFound : String
        
        if UserDefaults.standard.value(forKey: "my combo") != nil,
           let typeFoundTmp = UserDefaults.standard.string(forKey: "my combo") {
            typeFound = typeFoundTmp
        } else {
            typeFound = "unknown"
        }
        
        // pretend this is looking up something based on that string
        let foundData = "I looked this up using \(typeFound)"

        return foundData
    }
}

But then I realized that there was something else that I wanted to look up using the same value that was found via UserDefaults. I could make another computed property that copied the same UserDefaults code, but that was not appealing at all. I also could have created a function that looked up both things. But what I really wondered was “Can I modify my computed property to return both items? Can a computed property be a tuple?” I actually had no idea. As this stackoverflow post shows it turns out that a computed property can be a tuple!

February 4, 2022 Update

Wow. It turns out that you CAN use AppStorage with an enum. An enum can’t have STORED properties but it can have STATIC properties.

Here’s some sample code where I use AppStorage in an enum.

enum FancyPants {
    @AppStorage("cool") static var theNumber = 0
    
    case bird, turtle
    
    func mutation() {
        switch self {
            case .bird:
                print("bird \(FancyPants.theNumber)")
                FancyPants.theNumber += 1
            case .turtle:
                print("turtle \(FancyPants.theNumber)")
                FancyPants.theNumber += 10
        }
    }
}

I learned this from this video from Stewart Lynch.

Computed properties as tuples

Instead of having ‘computed’ be a String we’re now going to have it be a tuple with named parameters. By doing this I didn’t have to duplicate my code to look up data via UserDefaults.

enum FancyPants : String {
    case frozen = "Frozen Pizza"
    case fresh = "Ooni Pizza"

    var computed : (firstData : String, secondData : String) {
        let typeFound : String
        
        if UserDefaults.standard.value(forKey: "my combo") != nil,
           let typeFoundTmp = UserDefaults.standard.string(forKey: "my combo") {
            typeFound = typeFoundTmp
        } else {
            typeFound = "unknown"
        }
        
        // pretend this is looking up something based on that string
        let foundData = "I looked this up using \(typeFound)"
        // pretend this is also looking up something based on that string
        let additionalData = "I also looked this up using \(typeFound)"

        return (firstData: foundData, secondData: additionalData)
    }
}
let pizza = FancyPants.fresh
let x = pizza.computed.firstData
let y = pizza.computed.secondData

Xcode autocomplete

It turns out that Xcode autocomplete won’t offer suggestions for returning these tuple named parameters. Hopefully that will change in future releases.

Conclusion

I’m going to give my standard disclosure. Was this the best way to do this? Probably not. Is it the only way to do this? Absolutely not. Was it a great learning experience? 100% yes. When I Tweeted that computed properties could be tuples it got a good deal of attention so I thought I’d explain more of how I came across this concept.

If this post was helpful to you I’d love to hear about it! Find me on Twitter.