UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Multi dimensional array handling using "Old School" coding looking for SwiftUI ideas

Forums > SwiftUI

I have written code to select from two lists of strings, with two "boxes" on screen. It works, no problems. It would be a nightmare to add more selections. How would you write this using the swiftui coding phylosophy?
In my live app, theSelection is passed out thru the environment to other views to drive their appearance.

Hard coding like this just does not work so well

selections = [0,1]

selColors[selections[0]][0]

Just looking for ideas on how to handle these types of things

import SwiftUI

struct ContentView: View {
    @State private var seqs = [["Fairways","Greens","Putts","Score"],["Score","Putts","Fairways","Greens"]]
    @State private var theSelection = 0
    @State private var selections: [Int] = [0,1]
    @State private var selColors: [[Color]] = [[Color.red, Color.white],[Color.brown, Color.white]]
    var body: some View {
        HStack {
            VStack {
                ForEach(seqs[0], id: \.self) { name in
                    Text(name)
                }
            }
            .onTapGesture {
                theSelection = 0
                selections = [0,1]
            }
            .frame(width: 100)
            .cornerRadius(8)
            .background(LinearGradient(gradient: Gradient(
                colors: [
                selColors[selections[0]][0],
                selColors[selections[0]][1]
                        ]),
                startPoint: .top,
                endPoint: .bottom))
            VStack {
                ForEach(seqs[1], id: \.self) { name in
                    Text(name)
                }
            }
            .onTapGesture {
                theSelection = 1
                selections = [1,0]
            }
            .frame(width: 100)
            .cornerRadius(8)
            .background(LinearGradient(gradient: Gradient(
                colors: [
                selColors[selections[1]][0],
                selColors[selections[1]][1]
                        ]),
                startPoint: .top,
                endPoint: .bottom))
        }
    }
}

2      

The best way to reduce complexity in SwiftUI is to refactor your Views into multiple smaller Views that can be composed together. When you find yourself repeating something with only slight differences each time (like the VStack in your original code), think about extracting the commonalities to their own View and parameterize the differences.

It also helps to take advantage of Swift's powerful type system, especially enums, which can do a whole lot of surprising stuff.

Anyway, here's one way it could be done:

import SwiftUI

enum GolfSequenceItem: String, Identifiable {
    case fairways, greens, putts, score

    var id: String {
        self.rawValue
    }

    var name: String {
        self.rawValue.capitalized
    }

    //you can add more lists here and everything will just work
    static var lists: [[GolfSequenceItem]] {
        [
            [.fairways, .greens, .putts, .score],
            [.score, .putts, .fairways, .greens],
        ]
    }
}

//handles one list of items
struct SequenceList: View {
    //passed in from parent View
    let list: [GolfSequenceItem]
    let isSelected: Bool

    //set up in this View
    let selectedGradient = LinearGradient(
        gradient: Gradient(
            colors: [.red, .white]
        ),
        startPoint: .top,
        endPoint: .bottom
    )
    let unselectedGradient = LinearGradient(
        gradient: Gradient(
            colors: [.brown, .white]
        ),
        startPoint: .top,
        endPoint: .bottom
    )

    var body: some View {
        VStack {
            ForEach(list) { item in
                Text(item.name)
            }
        }
        .frame(width: 100)
        .background {
            (isSelected ? selectedGradient : unselectedGradient)
                .cornerRadius(8)
        }
    }
}

struct NewGolfView: View {

    @State private var whichListSelected = 0

    var body: some View {
        HStack {
            //we loop through all the lists provided by GolfSequence
            //and display them using a SequenceList View
            ForEach(0..<GolfSequence.lists.count) { idx in
                SequenceList(list: GolfSequence.lists[idx],
                             isSelected: whichListSelected == idx)
                //keep .onTapGesture here, where we track which
                //list is selected
                    .onTapGesture { whichListSelected = idx }
            }
        }
    }
}

struct NewGolfView_Previews: PreviewProvider {
    static var previews: some View {
        NewGolfView()
    }
}

I hope I've added enough commennts to explain what's going on. If you need more help understanding, don't hesitate to ask.

3      

slight typo in the foreach, s/b GolfSequenceItem, but other than that, works fine. Just what I was looking for. Old School was create a dimension in the array. Swift Scool, create a view of the dimension.

Lovely...

I've used enums for ages, but I'm still struggling with the power of them in the Swift environment.

Thanks for the lesson

2      

Ack, sorry about that. I was making tweaks and updating my post as I did so and must have gotten a little out of sync between Xcode and the post.

It should be:

            ForEach(0..<GolfSequenceItem.lists.count) { idx in
                SequenceList(list: GolfSequenceItem.lists[idx],
                             isSelected: whichListSelected == idx)
                //keep .onTapGesture here, where we track which
                //list is selected
                    .onTapGesture { whichListSelected = idx }
            }

Though just for giggles, here's what I tweaked it to that caused my mistake:

enum GolfSequence {
    enum Item: String, Identifiable {
        case fairways, greens, putts, score

        var id: String { self.rawValue }
        var name: String { self.rawValue.capitalized }
    }

    static var lists: [[Item]] {
        [
            [.fairways, .greens, .putts, .score],
            [.score, .putts, .fairways, .greens],
        ]
    }
}

And then in SequenceView:

let list: [GolfSequence.Item]

Either way works fine, but go with whatever works best for you. (I also looked at typealias GolfSequence = [GolfSequenceItem] but then realized I was needlessly bikeshedding.)

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.