Learn Courses My Dashboard

How to evaluate a string?

Hi, this is a follow-up to “Where do I put this conditional?” I’ve put in a function, setupB, to put in 81 buttons where the labels are made up from an 81 character string that summarizes either the original sudoku puzzle or the solution. My question is this: how do I get that code into a view as 81 buttons? Thank you.

   func setupB() -> String {
        var str = ""
        for i in 0..<81 {
            str += "Button {buttonWasTapped(currTag:" + String(i) + ")"
            str += "} label: {Text((showSolution ? solution : original).dropFirst(" + String(i) + ") .dropLast(" + String(80-i) + "))"
            str += ".foregroundColor(original.dropFirst(" + String(i) + ") .dropLast(" + String(80-i) + ") == \"0\" ? cDynamic : cStatic)"
            str += "}.tag(" + String(i) + ")  "
        }
        return str
    }
    
    var body: some View {
        VStack {
            Text("SUDOKU")
                .padding(.bottom, 55)
            Toggle("Show Solution", isOn: $showSolution)
                .toggleStyle(SwitchToggleStyle(tint: cDynamic))
                .padding(.leading, 42).padding(.trailing, 42)
            Text(setupB())
        }
    }

Full project at GitHub - 56phil/Sudoku-SwiftUI

There’s no way to do that in Swift. Swift is a compiled language that emphasizes, among other things, type safety and being able to turn random strings into executable code goes against that principle.

Plus, that’s just not the way to do something like that in SwiftUI. Instead, you would use a ForEach to loop through some collection of data and generate elements for your interface.

ForEach(Array(yourBigString), id: \.self) { character in
    //character is one character in the yourBigString
    Button {
        buttonWasTapped(currTag: String(character))
    } label: {
        Text(character)
            .foregroundColor(...)
    }
}

And you would use some logic outside the loop, like in a computed property or something, to determine if yourBigString would be the original or the solution.

Thanks, roosterboy, is there a way to enumerate character?

            var n = 0
            ForEach(Array(arrayLiteral: (showSolution ? solution : original)), id: \.self) { character in
                //character is one character in the yourBigString
                Button {
                    buttonWasTapped(currTag: n)
                    
                } label: {
                    Text(character)
                        .foregroundColor(character == "0" ? cDynamic : cStatic)
                }
//                n += 1  // can't do this in a view...
            }.tag(n)

//enumerated() gives back an array of tuples (offset, element)
//offset will be an Int
//element will be a Character
ForEach(Array((showSolution ? solution : original).enumerated()), id: \.offset) { (offset, element) in
    Button {
        buttonWasTapped(currTag: offset)
    } label: {
        Text(String(element))
            .foregroundColor(element == "0" ? cDynamic : cStatic)
    }
    .tag(offset)
}

But just look at this line:

ForEach(Array((showSolution ? solution : original).enumerated()), id: \.offset)

That’s gross. I would seriously suggest pulling that into some kind of helper, whether a computed property or a function, to hide the logic from the ForEach and just give it a collection to be looped through. The more logic you get out of a View's body property, the happier SwiftUI and the compiler will be.

That’s fantastic!. Thank you roosterboy. I’m still unclear on how I could place those buttons in their correct positions.

I’ll attach a screenshot from the simulator that I completed last night, Here’s a code segment that goes up to the the first three characters of the string:

import SwiftUI

struct ContentView: View {
    let cStatic:Color = .yellow
    let cError:Color = .red
    let cDynamic:Color = .brown
    let solution = String(cString: initPuzzle())
    let original = String(cString: getOriginal())
    @State private var showSolution = true
    
    func buttonWasTapped(currTag: Int) {
        print(currTag)
    }
    
    var body: some View {
        VStack {
            Text("SUDOKU")
                .padding(.bottom, 42)
            Toggle("Show Solution", isOn: $showSolution)
                .toggleStyle(SwitchToggleStyle(tint: cDynamic))
                .padding(.leading, 42).padding(.trailing, 42)
            VStack {
                HStack {
                    Spacer()
                    HStack {
                        Button {buttonWasTapped(currTag: 0 )
                        } label: {Text((showSolution ? solution : original).dropFirst(0) .dropLast(80))
                                .foregroundColor(original.dropFirst(0) .dropLast(80) == "0" ? cDynamic : cStatic)
                        }.tag(0)
                        
                        Button {buttonWasTapped(currTag: 1 )
                        } label: {Text((showSolution ? solution : original).dropFirst(1) .dropLast(79))
                                .foregroundColor(original.dropFirst(1) .dropLast(79) == "0" ? cDynamic : cStatic)
                        }.tag(1)
                        
                        Button {buttonWasTapped(currTag: 2 )
                        } label: {Text((showSolution ? solution : original).dropFirst(2) .dropLast(78))
                                .foregroundColor(original.dropFirst(2) .dropLast(78) == "0" ? cDynamic : cStatic)
                        }.tag(2)
                        
                    }
                    Spacer()

I’m sure that you’ll see this as even more “gross” than your snippet. That one liner is elegant. Thank you for your help. I’d be way behind without it.

Rather than hard-coding a bunch of HStacks, you would do something like what I posted here. It uses nested LazyVGrids to lay out all the numbers in their 9 9x9 grids.

Thank you for your patience. Clearly, I have a lot to learn. It’s a real challenge for a crusty old timer like me to learn all these new techniques.