-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement VisibilityThresholdModifier
To control whenever TrackViews will be taking up rendering tasks
- Loading branch information
1 parent
932804f
commit 4d8c789
Showing
7 changed files
with
154 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
Sources/StreamVideoSwiftUI/CallView/VisibilityThresholdModifier.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// Copyright © 2023 Stream.io Inc. All rights reserved. | ||
// | ||
|
||
import StreamVideo | ||
import SwiftUI | ||
|
||
/// The modifier designed to dynamically track and respond to the visibility status of a view within its parent | ||
/// bounds or viewport. It utilises a user-defined visibility threshold, represented as a percentage, to | ||
/// determine how much of the view should be visible (both vertically and horizontally) before it's considered | ||
/// "on screen". | ||
/// | ||
/// When the visibility state of the view changes (i.e., it transitions between being "on screen" and "off screen"), | ||
/// a callback is triggered to notify the user of this change. This can be particularly useful in scenarios where | ||
/// resource management is crucial, such as video playback or dynamic content loading, where actions might | ||
/// be triggered based on whether a view is currently visible to the user. | ||
/// | ||
/// By default, the threshold is set to 30%, meaning 30% of the view's dimensions must be within the parent's | ||
/// bounds for it to be considered visible. | ||
struct VisibilityThresholdModifier: ViewModifier { | ||
/// State to track if the content view is on screen. | ||
@State private var isOnScreen = false { | ||
didSet { | ||
// Check if the visibility state has changed. | ||
guard isOnScreen != oldValue else { return } | ||
// Notify the caller about the visibility state change. | ||
changeHandler(isOnScreen) | ||
} | ||
} | ||
|
||
/// The bounds of the parent view or viewport. | ||
var bounds: CGRect | ||
/// The threshold percentage of the view that must be visible. | ||
var threshold: CGFloat | ||
/// Closure to handle visibility changes. | ||
var changeHandler: (Bool) -> Void | ||
|
||
init(in bounds: CGRect, | ||
threshold: CGFloat, | ||
changeHandler: @escaping (Bool) -> Void | ||
) { | ||
self.bounds = bounds | ||
self.threshold = threshold | ||
self.changeHandler = changeHandler | ||
} | ||
|
||
func body(content: Content) -> some View { | ||
content | ||
.background( | ||
GeometryReader { geometry -> Color in | ||
/// Convert the local frame of the content to a global frame. | ||
let geometryInGlobal = geometry.frame(in: .global) | ||
/// Calculate the global minY, maxY, minX, and maxX of the content view. | ||
let minY = geometryInGlobal.minY | ||
let maxY = geometryInGlobal.maxY | ||
let minX = geometryInGlobal.minX | ||
let maxX = geometryInGlobal.maxX | ||
|
||
/// Calculate required height and width based on visibility threshold. | ||
let requiredHeight = geometry.size.height * threshold | ||
let requiredWidth = geometry.size.width * threshold | ||
|
||
/// Check if the content view is vertically within the parent's bounds. | ||
let verticalVisible = (minY + requiredHeight < bounds.maxY && minY > bounds.minY) || | ||
(maxY - requiredHeight > bounds.minY && maxY < bounds.maxY) | ||
/// Check if the content view is horizontally within the parent's bounds. | ||
let horizontalVisible = (minX + requiredWidth < bounds.maxX && minX > bounds.minX) || | ||
(maxX - requiredWidth > bounds.minX && maxX < bounds.maxX) | ||
|
||
/// Update the isOnScreen state based on visibility calculations. | ||
DispatchQueue.main.async { | ||
self.isOnScreen = verticalVisible && horizontalVisible | ||
} | ||
|
||
/// Use a clear color for the background to not affect the appearance. | ||
return Color.clear | ||
} | ||
) | ||
} | ||
} | ||
|
||
extension View { | ||
/// Attaches a visibility observation modifier to the view. | ||
/// | ||
/// - Parameters: | ||
/// - bounds: The bounds of the parent view or viewport within which the visibility of the view will | ||
/// be tracked. | ||
/// - threshold: A percentage value (defaulted to 0.3 or 30%) representing how much of the view | ||
/// should be visible within the `bounds` before it's considered "on screen". | ||
/// - changeHandler: A closure that gets triggered with a Boolean value indicating the visibility | ||
/// state of the view whenever it changes. | ||
/// | ||
/// - Returns: A modified view that observes its visibility status within the specified bounds. | ||
func visibilityObservation( | ||
in bounds: CGRect, | ||
threshold: CGFloat = 0.3, | ||
changeHandler: @escaping (Bool) -> Void | ||
) -> some View { | ||
modifier( | ||
VisibilityThresholdModifier( | ||
in: bounds, | ||
threshold: threshold, | ||
changeHandler: changeHandler | ||
) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters