Home > OS >  ForEach loop with dynamic variable
ForEach loop with dynamic variable

Time:05-26

I need to show two different images depending on an @State bool, so that when the user presses the image it shows the second image and when the user releases it goes back to showing the first image. This all works perfectly, however I have a situation where I need to do the same thing in a ForEach loop and since I don't believe Swift can handle dynamic variables, I can't see how to make this happen.

In the code below you'll see the struct of how the button is configured, and then it's called in the ForEach loop of the MainView. The btnHover variable needs to change for each iteration or else all the buttons show the hover image when a single one is pressed.

Also, I cannot place this into a separate struct and then call to it in the main view. I spent quite a bit of time going down that road today and it seems that if you call a NavigationLink from a separate struct, you can no longer pop to root. I don't understand it, but that's apparently the deal. So, I need to make this work in the main view, not in a separate struct.

The real code is quite a bit more complex (i.e. each button is actually a NavigationLink), but I tried to put together as simplistic of an example as I could.

struct PressActions: ViewModifier {
    var onPress: () -> Void
    var onRelease: () -> Void

    func body(content: Content) -> some View {
        content
            .simultaneousGesture(
                DragGesture(minimumDistance: 0)
                    .onChanged({ _ in
                        onPress()
                    })
                    .onEnded({ _ in
                        onRelease()
                    })
            )
    }
}

extension View {
    func pressAction(onPress: @escaping (() -> Void), onRelease: @escaping (() -> Void)) -> some View {
        modifier(PressActions(onPress: {
            onPress()
        }, onRelease: {
            onRelease()
        }))
    }
}

struct DynamicBtn: View {
    var btnName: Bool
    var body: some View {
        Image(btnName == true ? "DynamicHover" : "Dynamic")
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}

struct MainView: View {
    @State private var btnHover: Bool = false
    let user = [Tim, John, Joe, Mary]
    var body: some View {
        ForEach(user, id: \.self) { user in
            VStack {
                DynamicBtn(btnName: btnHover)
                .pressAction {
                    btnHover = true
                } onRelease: {
                    btnHover = false
                }
            }
        }
    }
}

CodePudding user response:

try something like this approach, to be able to "press" on your
DynamicBtn and show a particular image, and then release the "press" to display the original image:

struct MainView: View {
    let users = [User(name: "Tim"), User(name: "John"), User(name: "Joe"), User(name: "Mary")]
    @State var selectedUser: User?  // <-- here
    
    var body: some View {
        ForEach(users, id: \.self) { user in
            DynamicBtn(btnName: selectedUser == user) // <-- here
                .pressAction {
                    selectedUser = user  // <-- here
                } onRelease: {
                    selectedUser = nil   // <-- here
                }
        }
    }
}

// for testing
struct User: Identifiable, Hashable {
    let id = UUID()
    var name = ""
}

Note: you can just use Image(btnName ? "DynamicHover" : "Dynamic") in DynamicBtn.

  • Related