ios – SwiftUI tapping a Button always selects the last button in a view


I posted a similar question last night and received several comments from users who could not reproduce the issue. Below is an MRE (really a minimal unreproducible example) which I assume is analogous to what these users tested:

import SwiftUI

@main
struct ColorPickerTestApp: App {
    @StateObject private var accentColorManager = AccentColorManager()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(accentColorManager)

        }
    }
}

class AccentColorManager: ObservableObject {
    
    let colors: [Color] = [.orange, .yellow, .green, .blue, .purple, .brown, .cyan, .indigo, .mint, .teal]

    @Published var accentColor: Color = .orange // on initial launch orange is default color

    @Published var accentColorIndex: Int = 0 {
        didSet {
            accentColor = colors[accentColorIndex]
            saveAccentColorIndex()
            objectWillChange.send()
        }
    }

    init() {
        loadAccentColorIndex()
    }
    
    private let accentColorIndexKey = "AccentColorIndex"
    
    private func saveAccentColorIndex() {
        UserDefaults.standard.set(accentColorIndex, forKey: accentColorIndexKey)
    }

    private func loadAccentColorIndex() {
        if let storedIndex = UserDefaults.standard.value(forKey: accentColorIndexKey) as? Int {
            accentColorIndex = storedIndex
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var accentColorManager: AccentColorManager

    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(accentColorManager.accentColor)
            Text("Hello, world!")
                .foregroundStyle(accentColorManager.accentColor)
            Spacer()
            ActiveColorSectionView()
        }
        .padding()
    }
}

struct ActiveColorSectionView: View {
    @EnvironmentObject var accentColorManager: AccentColorManager

    var body: some View {
        Section(header: Text("Active Color")) {
            VStack(alignment: .leading) {
                ForEach(accentColorManager.colors.indices, id: \.self) { index in
                    Button(action: {
                        accentColorManager.accentColorIndex = index
                    }) {
                        HStack {
                            Text(accentColorManager.colors[index].description)
                            Spacer()
                            Circle()
                                .frame(width: 20, height: 20)
                                .foregroundColor(accentColorManager.accentColorIndex == index ? accentColorManager.colors[index] : .clear)
                                .overlay(
                                    Circle()
                                        .stroke(Color.primary, lineWidth: 2)
                                )
                        }
                    }
                    .foregroundColor(accentColorManager.colors[index])
                    .padding(.vertical, 5)
                }
            }
        }
        .listRowBackground(Color(UIColor.systemBackground))
    }
}

This works beautifully. What I cannot figure out is why the exact code behaves differently in the context of my app. In my app, selecting a button from the list of colors always results in every single button present in the view (regardless of whether a ForEach is used, or if I manually code several buttons) being pressed until ultimately the final button is pressed resulting in the final color displayed in the list being set as the accent color in my app.

Initially I assumed this was a problem with a closure capture related to the ForEach but I deliberately removed the ForEach and used a VStack of buttons and the same issue persisted: the final button in the VStack was always selected, not to mention the ForEach works as expected in the MRE.

Debugging my project I can press a button and then watch as all buttons are sequentially “pressed” in my project. Can anyone provide suggestions as how to further debug and/or resolve this issue?

More code and context describing my app:

My app’s main screen has an icon which navigates to a settings view:

struct SettingsView: View {
    @StateObject var savedLocations = SavedLocations()
    @ObservedObject var networking: Networking
    @EnvironmentObject var accentColorManager: AccentColorManager
    
    var body: some View {
        VStack {
            Form {
                FrostAlertsSectionView(savedLocations: savedLocations, networking: networking)
                ActiveColorSectionView()
            }
            
        }
        .navigationTitle("Settings")
        .accentColor(accentColorManager.accentColor)
    }
}

Aside from that my app contains one dozen views each expecting the AccentColorManager Environment Object. A link to a branch on GitHub that I expect will reproduce the issue (has my current broken code) is here.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img