SwiftUI: Prevent .onHover on overlapping views

Viewed 96

I have two views that both react to .onHover overlapping in a ZStack. Currently the effect is triggered in the blue view even if it is overlapped by the red view (see video). How can I make sure that only the view at the top of the ZStack will react to .onHover, so that the blue view will not change when the overlapping part of the red view is hovered?

enter image description here

Code:

struct ContentView: View {
    var body: some View {
        ZStack {
            RectangleView(color: .blue)
                .frame(width: 300, height: 300)
            RectangleView(color: .red)
                .frame(width: 150, height: 150)
                .offset(x: -150)
        }
        .frame(width: 600, height: 400)
    }
}

struct RectangleView: View {
    @State var hover = false
    var color: Color

    var body: some View {
        Rectangle()
            .foregroundColor(color)
            .overlay(hover ? Color.white.opacity(0.3) : Color.clear)
            .onHover { isHovering in
                if isHovering {
                    hover = true
                } else {
                    hover = false
                }
            }
    }
}
1 Answers

Here is a way that is felixaeble with deferent scenario:

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            
            RectangleView(color: .blue, index: 0)
                .frame(width: 300, height: 300)
            
            RectangleView(color: .red, index: 1)
                .frame(width: 150, height: 150)
                .offset(x: -150)
            
            RectangleView(color: .black, index: 2)
                .frame(width: 50, height: 50)
                .offset(x: -150)
            
            RectangleView(color: .green, index: 3)
                .frame(width: 150, height: 150)
                .offset(x: 150, y: 150)
            
        }
        .frame(width: 600, height: 600)
    }
}

struct RectangleView: View {
    let color: Color
    let index: Int
    @StateObject var observableHoveringObject: ObservableHoveringObject = ObservableHoveringObject.shared
    var body: some View {
        Rectangle()
            .foregroundColor(color)
            .overlay(observableHoveringObject.isHovering(index: index) ? Color.white.opacity(0.3) : Color.clear)
            .onHover { hoverValue in
                observableHoveringObject.sign(index, hoverValue: hoverValue)
            }
    }
}


class ObservableHoveringObject: ObservableObject {
    
    static let shared: ObservableHoveringObject = ObservableHoveringObject()
    
    @Published var activeHoveringIndex: Int? = nil
    
    
    func isHovering(index: Int) -> Bool {
        return index == activeHoveringIndex
    }
    
    private func exited(_ index: Int) { setCollection.remove(index) }
    
    private func entered(_ index: Int) { isHoveringTrafficLightFunction(index: index) }
    
    func sign(_ index: Int, hoverValue: Bool) {
        if (hoverValue) { entered(index) }
        else { exited(index) }
    }
    
    private var setCollection: Set<Int> = Set<Int>() {
        
        didSet {
            
            let filteredSet: Set<Int>.Element? = setCollection.max(by: { (lhs, rhs) in
                if (lhs < rhs) { return true }
                else { return false }
            })
            
            if let unwrappedValue: Int = filteredSet { activeHoveringIndex = unwrappedValue }
            else { activeHoveringIndex = nil }
            
        }
        
    }
    
    private func isHoveringTrafficLightFunction(index: Int) {
        
        if let unwrappedActiveHoveringIndex: Int = activeHoveringIndex {
            
            setCollection.insert(index)
            
            if (index >= unwrappedActiveHoveringIndex) { activeHoveringIndex = index }
            
        }
        else {
            activeHoveringIndex = index
            setCollection.insert(index)
            
        }
        
    }
}

enter image description here

Related