0

In my ContentView I have two buttons, red and green, when I click on each of them i change and then append them to an empty array that gets filled and display its items in my SecondView.

I would like to show a green name when i tap on the green button and a red one when i tap on the red button, how can i achieve this?

Here is the code:

ContentView

struct ContentView: View {
    @State private var countdown = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect().eraseToAnyPublisher()
    @State private var timeRemaining = 10
    @State private var isActive = false
    
    @State private var names = ["Steve", "Bill", "Jeff", "Elon", "Michael", "John", "Steven", "Paul"]
    @State private var appearedNames = [String]()
    @State private var currentPerson = ""
    
    var body: some View {
        NavigationView {
            ZStack {
                NavigationLink(destination: SecondView(appearedNames: $appearedNames, countdown: countdown), isActive: $isActive) {
                    EmptyView()
                }
                VStack {
                    Text("\(timeRemaining)")
                        .font(.largeTitle)
                        .padding()
                        .onReceive(countdown) { _ in
                            if timeRemaining > 0 {
                                timeRemaining -= 1
                            }
                            if timeRemaining == 0 {
                                isActive = true
                            }
                        }
                    
                    HStack {
                        Button {
                            appearedNames.append(currentPerson)
                            changePerson()
                        } label: {
                            Text(currentPerson)
                                .foregroundColor(.red)
                                .font(.largeTitle)
                        }
                        
                        Button {
                            appearedNames.append(currentPerson)
                            changePerson()
                        } label: {
                            Text(currentPerson)
                                .foregroundColor(.green)
                                .font(.largeTitle)
                        }
                    }
                }
                .onAppear {
                    changePerson()
                }
            }
        }
    }
    
    func changePerson() {
        if names.count > 0 {
            let index = Int.random(in: 0..<names.count)
            currentPerson = names[index]
            names.remove(at: index)
        }
    }
}

SecondView

import SwiftUI
import Combine

struct SecondView: View {
    @Binding var appearedNames: [String]
    var countdown: AnyPublisher<Date,Never>
    @State private var counter = 0
    
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                ScrollView(showsIndicators: false) {
                    ForEach(0...counter, id: \.self) { index in
                        Text(appearedNames[index])
                            .font(.system(size: 70))
                            .id(index)
                        
                            .onAppear {
                                proxy.scrollTo(index)
                            }
                    }
                }
            }
        }
        .navigationBarBackButtonHidden(true)
        .onReceive(countdown) { _ in
            if counter < appearedNames.count - 1 {
                counter += 1
            }
        }
    }
}
burnsi
  • 6,194
  • 13
  • 17
  • 27
simonaus
  • 43
  • 5
  • 1
    I have edited your question to include the detail you have provided in the new question. These should have been part of this question all along. – burnsi Aug 27 '22 at 02:21

1 Answers1

1

Instead of [String] store your choice in a struct with the chosen color:

// use this struct to save the chosen color with the name
struct ColoredName: Identifiable{
    let id = UUID()
    var name: String
    var color: Color
}

struct ContentView: View {
    @State private var countdown = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect().eraseToAnyPublisher()
    @State private var timeRemaining = 10
    @State private var isActive = false
    
    @State private var names = ["Steve", "Bill", "Jeff", "Elon", "Michael", "John", "Steven", "Paul"]
    //change this to the appropriate type
    @State private var appearedNames = [ColoredName]()
    @State private var currentPerson = ""
    
    var body: some View {
        NavigationView {
            ZStack {
                //pass the array on to the second view
                NavigationLink(destination: SecondView(appearedNames: $appearedNames, countdown: countdown), isActive: $isActive) {
                    EmptyView()
                }
                VStack {
                    Text("\(timeRemaining)")
                        .font(.largeTitle)
                        .padding()
                        .onReceive(countdown) { _ in
                            if timeRemaining > 0 {
                                timeRemaining -= 1
                            }
                            if timeRemaining == 0 {
                                isActive = true
                            }
                        }
                    
                    HStack {
                        Button {
                            //Create and append your struct to the array
                            appearedNames.append(ColoredName(name: currentPerson, color: .red))
                            changePerson()
                        } label: {
                            Text(currentPerson)
                                .foregroundColor(.red)
                                .font(.largeTitle)
                        }
                        
                        Button {
                            //Create and append your struct to the array
                            appearedNames.append(ColoredName(name: currentPerson, color: .green))
                            changePerson()
                        } label: {
                            Text(currentPerson)
                                .foregroundColor(.green)
                                .font(.largeTitle)
                        }
                    }
                }
                .onAppear {
                    changePerson()
                }
            }
        }
    }
    
    func changePerson() {
        if names.count > 0 {
            let index = Int.random(in: 0..<names.count)
            currentPerson = names[index]
            names.remove(at: index)
        }
    }
}

struct SecondView: View {
    @Binding var appearedNames: [ColoredName]
    var countdown: AnyPublisher<Date,Never>
    @State private var counter = 0
    
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                ScrollView(showsIndicators: false) {
                    ForEach(0...counter, id: \.self) { index in
                        //Access the properties of the colored name by index
                        Text(appearedNames[index].name)
                            .font(.system(size: 70))
                            .foregroundColor(appearedNames[index].color)
                            .id(index)
                            .onAppear {
                                proxy.scrollTo(index)
                            }
                    }
                }
            }
        }
        .navigationBarBackButtonHidden(true)
        .onReceive(countdown) { _ in
            if counter < appearedNames.count - 1 {
                counter += 1
            }
        }
    }
}

But I do not think iterating over an index is a good solution in any case. To solve your problem with the scrolling you can do:

struct SecondView: View {
    // This does not have to be a @Binding as long as you do not
    // manipulate the array from this view
    // this holds the source for the names to present
    @Binding var appearedNames: [ColoredName]
        
    var countdown: AnyPublisher<Date,Never>
    
    //Add this to show the names
    @State private var presentedPersons : [ColoredName] = []
    @State private var counter = 0
    
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                ScrollView(showsIndicators: false) {
                    // iterate over the array
                    ForEach(presentedPersons) { person in
                        //Access the properties of the struct to populate and format
                        // the text
                        Text(person.name)
                            // No need to set the `id` manually as our struct allready
                            // conforms to Identifiable
                            .font(.system(size: 70))
                            .foregroundColor(person.color)
                            .onAppear {
                                // As the struct is Identifiable the id of the View is the
                                // id of the struct and we can scroll by
                                proxy.scrollTo(person.id)
                            }
                    }
                }
            }
        }
        .navigationBarBackButtonHidden(true)
        .onReceive(countdown) { _ in
            if counter < appearedNames.count{
                presentedPersons.append(appearedNames[counter])
                counter += 1
            }
        }
    }
}
burnsi
  • 6,194
  • 13
  • 17
  • 27
  • I have 2 problems: FIRST: How can i check if ```appearedNames``` contains ```currentPerson```? I was trying with ```appearedNames.contains(currentPerson)``` but i got the error: *Cannot convert value of type 'String' to expected argument type '(ColoredName) throws -> Bool'*. SECOND: In the SecondView i cannot iterate over ```appearedNames``` array because i'm iterating over a counter to show names once at a time (this is the code i'm using: https://stackoverflow.com/a/73379450/18758938), is there another way to get an instance of the struct? – simonaus Aug 24 '22 at 14:54
  • @simonaus I think I have answered the question asked here. If you have another question please create a new one. Your first question in the comment seems to be a candidate for that. For your second question: I don´t know what you mean by that. If this question helped you solve your problem please consider marking it as accepted. – burnsi Aug 24 '22 at 18:42
  • `appearedNames` is an array. You can access the individual objects like any other array. – burnsi Aug 26 '22 at 16:54
  • Isn't there another way to change color when appending, without switching from ```[String]``` to ```[ColoredName]``` ? – simonaus Aug 26 '22 at 17:43
  • 1
    @simonaus I have edited my answer to include a solution wiht the details you provided. The most simple difference boils down to accessing the properties by `appearedNames[index].color`. – burnsi Aug 27 '22 at 02:25
  • Why in SecondView when saying ```.foregroundColor(person.color)``` i get this error: *Instance method 'id' requires that 'ColoredName' conform to 'Hashable'* ? – simonaus Aug 27 '22 at 12:57
  • @simonaus I just copy/pasted my answer into a new project and everything is working just fine. You probably modified the answer in a way that invalidates its design. But you could still conform the `ColoredName` struct to `Hashable`. e.g. `struct ColoredName: Identifiable, Hashable{` – burnsi Aug 27 '22 at 13:38
  • Yep i did it, thank you so much for the effort you put into helping me! I have one last question though, I used the second solution you provided for the SecondView, but why you don't think iterating over an index is a good solution? The code you provided works fine and the changes affect much less code... – simonaus Aug 27 '22 at 14:09
  • @simonaus It´s error prone and makes things more difficult then they should be. – burnsi Aug 27 '22 at 14:19