0

In a SwiftUI app I have a few buttons (let us say 3 as an example). One of them is highlighted.

When I tap on a non-highlighted button, the previously highlighted button toggles to non-highlighted and the tapped button becomes highlighted. If I tap the already highlighted button, nothing happens.

This scenario is simple and I have a highlighBtn variable, when a button is tapped highlighBtn takes the value of the tapped button. This variable is then used when the next tap happens to toggle off the previously highlighted button.

This cycle is OK, but the problem is when I do the first tap. For some reasons, things don't work.

This is how I handle the creation of the highlighBtn variable:

class ActiveButton: ObservableObject {
    @Published var highlighBtn = Button(....)
}

@StateObject var box = ActiveButton()

Here is the relevant code, when the button is tapped:

@EnvironmentObject var box: ActiveButton
....
Button(action: {
    // Toggle off the previously highlighted button.
    box.highlighBtn.highLight = false
    .... some useful processing ....
    box.highlighBtn = self
})

One detail I should give: if I tap the highlighted button to start, then all works as it should.

I have tried various method to solve this apparently simple problem but failed.

The first method was to try to initialize the highlighBtn variable. The second was to try to simulate a tap on the highlighted button.

I must be missing something simple. Any tip would be welcome.

After further investigation .....

I have created a demo app to expose my problem. Since I lack practice using GitHub, it took me some time, but it is now available here.

For that I created a SwiftUI app in Xcode.

In SceneDelegate.swift, the four lines of code right after this one have been customized for the needs of this app:

// Create the SwiftUI view that provides the window contents.

Beside that all I did resides inside the file ContentView.swift.

To save some time to anyone who is going to take a look; here is the way to get to the point (i.e. the issue I am facing).

  1. Run the app (I did it on an iPhone 7). You will see seven buttons appear. One of them (at random) will be highlighted. Starting with the highlighted button, tap on a few buttons one after another as many times as you want. You will see how the app is supposed to work.

  2. (After switching it off) Run the app a second time. This time you will also tap on a few buttons one after another as many times as you want; but start by tapping one of the non-highlighted button. You will see the problem appear at this point.

3
  • 3
    could you provide minimal reproducible example? I'm not sure what's highLight variable you're setting to false, and storing SwiftUI view inside an ObservableObject doesn't seems correct to me.. Commented Aug 21, 2021 at 9:26
  • Your issue seems to be answered so easily and not difficult issue, but the way you asked and your example of issue is not clear, you can make a minimal working issue instead. Commented Aug 21, 2021 at 10:50
  • @Philip_Dukhov, swiftPunk. I have completed the post and provided a demo app on GitHub, I hope you can take a look and let me know what you think. Commented Aug 24, 2021 at 1:50

1 Answer 1

1

Here is a solution for the first part of your question: Three buttons where the last one tapped gets highlighted with a background:

import SwiftUI

struct ContentView: View {

    enum HighlightTag {
        case none, first, second, third
    }

    @State private var highlighted = HighlightTag.none

    var body: some View {
        VStack {
            Button("First") {
                highlighted = .first
            }.background(
                Color.accentColor.opacity(
                    highlighted == .first ? 0.2 : 0.0
                )
            )
            Button("Second") {
                highlighted = .second
            }.background(
                Color.accentColor.opacity(
                    highlighted == .second ? 0.2 : 0.0
                )
            )
            Button("Third") {
                highlighted = .third
            }.background(
                Color.accentColor.opacity(
                    highlighted == .third ? 0.2 : 0.0
                )
            )
        }
    }
}

Update: After reviewing your sample code on GitHub, I tried to understand your code, I tried to make some simplifications and tried to find a working solution.

Here are some opinions:

  • The Attribute "@State" in front of "var butnsPool" is not needed and confusing.
  • The Attribute "@State" in front of "var initialHilight" is not needed and confusing.
  • Your ActiveButton stores a copy of the selected Button View because it is a struct which is probably the main reason for the strange behaviour.
  • The needInit in your ObservableObject smells bad at least. If you really need to initialize something, you may consider doing it with some .onAppear() modifier in you ContentView.
  • There is probably no need to use .environmentObject and @EnvironmentObject. You could consider using a parameter and @ObsservedObject
  • There is probably no need for the ActiveButton at all, if you only use it internally. You could consider using a @State with the selected utton name
  • Your BtnTxtView is fine, but consider replacing the conditional (func if) with some animatable properties, if you want to animate the transition.

Based on your code I created a much simpler and working solution.

I removed the ActiveButton class and also the BttnView struct. And I replaced the ContentView with this:

struct ContentView: View {
    var butnsPool: [String]
    var initialHilight: Int
    @State var selectedBox: String = ""

    var body: some View {
        ForEach(butnsPool, id: \.self) { buttonName in
            Button(action: {
                selectedBox = buttonName
            })
            {
                BtnTxtView(theLabel: buttonName,
                           highLight: buttonName == selectedBox)
            }
        }.onAppear {
            selectedBox = butnsPool[initialHilight]
        }
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

True, but I want a more generic solution. This one has a fix number of buttons. If you have the time you can take a look at a demo I just provided. See the updated post.
Hey @Michel, my solution above is working for the problem you described in the first sentences of your question. Its on purpose not a generic solution. It should be easy to find a clean generic solution if you have a collection of identifiable button items , but that is not what you asked at first. Can you see some value in my solution so far?
Hi @Roland_Schmitz, I am not totally sure what you are thinking of when you write "the first sentences of your question". The simplest way I can tell what I need is : a solution making my demo work or/and an explanation as why it does not work.
I meant the first 4 sentences from your question where you described what you wanted to achieve. From my perspective I provided a working solution for your scenario. It was on purpose not generic to make it as simple as possible. So I was wondering why you did not see any value in this solution. I could not do much more at that time, because your initial sample code did not include all the relevant parts.
Hey @Michel, I reviewed your code and found some things to consider and also a working solution. Have a look on my updated answer. I hope this provides more value to you than my original answer. Give it a try!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.