Search code examples
swiftuiviewmodifier

SwiftUI `ViewModifier` detect if `content` has `opacity` 0


I'm trying to write a custom ViewModifier where my implementation needs to detect if the content it is modifying is visible or not. This is not an ordinary ViewModifier where it's irrelevant if the content is visible; the modifier is attempting to track visibility for the purpose of determining if the underlying content should be streamed via video capture.

(It's still confusing to me why Apple did not provide a .visible(_ visibility: Visibility) with enum Visibility { case visible, case hidden, case collapsed }, and instead encouraged using .opacity(_:) to conditionally hide views. But that's another story.)

In any case, while I can set the opacity with .opacity(_:), I can't get the contents opacity. Note that onAppear is called, even for views with opacity set to 0.

struct MyViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .mymodifierimplemtation()
            .onAppear() {
                // Called even if content opacity is 0
                print("Appeared")
            }
    }
}

Hopefully this makes sense. Any assistance appreciated.


Solution

  • You can create a custom modifier to set the view's opacity to 0 and use EnvironmentValues to track the visibility of this view in your ViewModifier using:

    @Environment(\.visibility) var visibility
    

    Environment Key:

    struct VisibilityEnvironmentKey: EnvironmentKey {
        static let defaultValue = Visibility.automatic
    }
    
    extension EnvironmentValues {
        var visibility: Visibility {
            get { 
                self[VisibilityEnvironmentKey.self]
            }
            set { 
                self[VisibilityEnvironmentKey.self] = newValue 
            }
        }
    }
    

    Visibility Modifier:

    extension View {
        @ViewBuilder func visible(_ visibility: Visibility) -> some View {
            Group {
                if visibility != .automatic {
                    self
                        .opacity(visibility == .hidden ? 0: 1)
                }
            }.environment(\.visibility, visibility)
        }
    }