Initialize a property with a function in SwiftUI?

Viewed 497

I have a simple SwiftUI view which displays three number positions and a refresh button. The refresh button runs a pickThree() function which replaces each number with a random one.

In order to start the view, I need to give each position a starting number, as shown in the allVals variable.

I want to start the view with a random number in each position, and not the hard-coded numbers from the allVals variable. It seems that I should be able to do this by running the same pickThree() refresh function… perhaps by setting it as the value of the allVals variable upfront:

@State var allVals = pickThree()

This produces an error:

Cannot use instance member 'pickThree' within property initializer; property initializers run before 'self' is available

What am I missing here?

import SwiftUI

struct ThreeCards: View {
    // @State var allVals = pickThree() // PRODUCES ERROR
    @State var allVals = [1,2,3]
    
    var body: some View {
        VStack {
            HStack {
                Text(String(allVals[0]))
                Text(String(allVals[1]))
                Text(String(allVals[2]))
            }
            
            Button(action: {
                pickThree()
            }) {
                Text("Refresh")
            }
        }
    }
    
    func pickThree() -> [Int] {
        let one = Int.random(in: 1...3)
        let two = Int.random(in: 1...3)
        let three = Int.random(in: 1...3)
        
        allVals = [one, two, three]
        
        return allVals
    }
}

  
1 Answers

The statement @State var allVals = pickThree() implicitly requires self to initialize the instance variable allVals, but when this happens self is not yet available.

There are several approaches.

  1. Use static function instead:

This applies when you don't need access to other instance variables.

struct ThreeCards: View {
    @State var allVals = ThreeCards.pickThree() 
    
    var body: some View {
        VStack {
            HStack {
                Text(String(allVals[0]))
                Text(String(allVals[1]))
                Text(String(allVals[2]))
            }
            
            Button(action: {
                allVals = ThreeCards.pickThree()
            }) {
                Text("Refresh")
            }
        }
    }
    
    static func pickThree() -> [Int] {
        let one = Int.random(in: 1...3)
        let two = Int.random(in: 1...3)
        let three = Int.random(in: 1...3)
        return [one, two, three]
    }
}
  1. Initialize the instance variable in init:
struct ThreeCards: View {
    @State var allVals = [Int.random(in: 1...3), Int.random(in: 1...3), Int.random(in: 1...3)]
    
    var body: some View {
        VStack {
            HStack {
                Text(String(allVals[0]))
                Text(String(allVals[1]))
                Text(String(allVals[2]))
            }
            
            Button(action: {
                allVals = pickThree()
            }) {
                Text("Refresh")
            }
        }
    }
    
    init() {
        allVals = pickThree()
    }
    
    func pickThree() -> [Int] {
        let one = Int.random(in: 1...3)
        let two = Int.random(in: 1...3)
        let three = Int.random(in: 1...3)
        return [one, two, three]
    }
}
  1. A much neater one, for this particular purpose:
struct ThreeCards: View {
    @State var digits = ["1", "2", "3"].shuffled()
    
    var body: some View {
        VStack {
            HStack {
                Text(digits[0])
                Text(digits[1])
                Text(digits[2])
            }
            
            Button(action: {
                digits.shuffle()
            }) {
                Text("Refresh")
            }
        }
    }
}
Related