4

I am trying to animate individual items on mouseover. The issue I am having is that every item gets animated on mouseover of an item instead of just that specific item. Here is what I have:

struct ContentView : View {
    @State var hovered = false
    var body: some View {
        VStack(spacing: 90) {
            ForEach(0..<2) {_ in
                HStack(spacing: 90) {
                    ForEach(0..<4) {_ in
                        Circle().fill(Color.red).frame(width: 50, height: 50)
                            .scaleEffect(self.hovered ? 2.0 : 1.0)
                        .animation(.default)
                        .onHover { hover in
                                print("Mouse hover: \(hover)")
                            self.hovered.toggle()
                        }
                    }
                }
            }
        }
        .frame(minWidth:300,maxWidth:.infinity,minHeight:300,maxHeight:.infinity)
    }
}




drawing

Isaac
  • 645
  • 1
  • 7
  • 17

3 Answers3

8

It needs to change onHover view on per-view base, ie. store some identifier of hovered view.

Here is possible solution. Tested with Xcode 11.4.

demo

struct TestOnHoverInList : View {
    @State var hovered: (Int, Int) = (-1, -1)
    var body: some View {
        VStack(spacing: 90) {
            ForEach(0..<2) {i in
                HStack(spacing: 90) {
                    ForEach(0..<4) {j in
                        Circle().fill(Color.red).frame(width: 50, height: 50)
                        .scaleEffect(self.hovered == (i,j) ? 2.0 : 1.0)
                        .animation(.default)
                        .onHover { hover in
                            print("Mouse hover: \(hover)")
                            if hover {
                                self.hovered = (i, j)    // << here !!
                            } else {
                                self.hovered = (-1, -1)  // reset
                            }
                        }
                    }
                }
            }
        }
        .frame(minWidth:300,maxWidth:.infinity,minHeight:300,maxHeight:.infinity)
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
5

Every item currently gets animated because they are all relying on hovered to see if the Circle is hovered over. To fix that, we can make every circle have their own hovered state.

struct CircleView: View {
    @State var hovered = false

    var body: some View {
        Circle().fill(Color.red).frame(width: 50, height: 50)
            .scaleEffect(self.hovered ? 2.0 : 1.0)
        .animation(.default)
        .onHover { hover in
                print("Mouse hover: \(hover)")
            self.hovered.toggle()
        }
    }
}

and in the ForEach we can just call the new CircleView where every Circle has their own source of truth.

struct ContentView : View {
    var body: some View {
        VStack(spacing: 90) {
            ForEach(0..<2) { _ in
                HStack(spacing: 90) {
                    ForEach(0..<4) { _ in
                        CircleView()
                    }
                }
            }
        }
        .frame(minWidth:300,maxWidth:.infinity,minHeight:300,maxHeight:.infinity)
    }
}
sfung3
  • 2,227
  • 1
  • 9
  • 30
0

Alternatively, you can create a modifier that allows you to change the View in question when it's hovered:

extension View {
    func onHover<Content: View>(@ViewBuilder _ modify: @escaping (Self) -> Content) -> some View {
        modifier(HoverModifier { modify(self) })
    }
}

private struct HoverModifier<Result: View>: ViewModifier {
    @ViewBuilder let modifier: () -> Result
    @State private var isHovering = false

    func body(content: Content) -> AnyView {
        (isHovering ? modifier().eraseToAnyView() : content.eraseToAnyView())
            .onHover { self.isHovering = $0 }
            .eraseToAnyView()
    }
}

Then each Circle on your example would go something like:

Circle().fill(Color.red).frame(width: 50, height: 50)
    .animation(.default)
    .onHover { view in
        _ = print("Mouse hover")
        view.scaleEffect(2.0)
    }