iOS Masking Best Practices
In Glance Mobile Cobrowse, masking is a critical feature used to protect sensitive or private information on the end user’s screen during a live session with an agent. By selectively hiding specific UI elements—such as passwords, credit card fields, or health data—from the representative’s view, masking enables companies to safeguard user privacy while still delivering real-time support.
To enable masking during sessions, developers should use the Glance Mobile SDK to specify which parts of the screen should be hidden from agents. Masking integrates directly with the app’s native rendering layer, allowing developers to selectively obscure sensitive views. There are four primary contexts in which masking can be applied:
- UIKit Masking
- SwiftUI Masking
- WebView Masking
- Keyboard Masking
Below are best practices for each of these contexts, along with general masking guidelines that apply in all situations.
General Masking
In all contexts, once an element is designated to be masked by the SDK, it remains hidden from the agent’s view for the duration of the current session and any future sessions unless explicitly removed. Because of this behavior, masking should be applied during the creation of views—either at layout or during initialization—rather than waiting until a session begins. This ensures that masking is consistently applied, even if a session starts after views have already been rendered, or if views are recreated during the app lifecycle.
Similarly, masking should be removed when a view is destroyed or removed from the view hierarchy. In most cases, masking is automatically removed when the associated view is deallocated. However, best practice is to explicitly remove the mask in the view’s destructor or lifecycle cleanup logic.
Masking will always be active when the view is being displayed. If a view is destroyed or no longer present in the hierarchy, the mask will be removed and will not appear during sessions. However, there are cases where a mask may appear even though the view is not visibly present—typically because it is being covered by another view. Since the SDK cannot always detect when a view is visually obstructed, developers should manually remove and reapply masking when visibility changes and masking is not desired.
Takeaways
- Masked elements remain hidden for the entire session once registered with the SDK.
- Apply masking during view initialization or layout, not after a session begins.
- Always remove masking when a view is destroyed or removed from the hierarchy.
UIKit Masking
For apps built using UIKit (iOS), masking is applied by passing references to views directly to the SDK. Masking can be removed by calling the appropriate SDK methods and providing references to the views to be unmasked.
Below are the Swift methods that should be used for masking UIKit-based views:
// Adding Masked Views
Glance.addMaskedView(textField, withLabel: "Masked text field")
Glance.addMaskedView(label, withLabel: "Masked label")
Glance.addMaskedView(button, withLabel: "Masked button")
// Removing Masked Views
Glance.removeMaskedView(textField)
Glance.removeMaskedView(label)
Glance.removeMaskedView(button)
When using UIKit, the SDK relies on the native view hierarchy to determine which views should be masked. Typically, you do not need to manually add or remove masked views based on their visibility—this is handled automatically. However, in certain edge cases, masks may remain visible even when the corresponding views are actually obstructed by another view.
This situation can occur with elements like UIAlertView
or with SwiftUI views layered on top of UIKit views in a way that prevents the SDK from detecting the obstruction. In such cases, you may need to explicitly call removeMaskedView
when the view is no longer visible, and addMaskedView
when it becomes visible again.
Note: This is a rare situation. In most cases, manual intervention is not required.
SwiftUI Masking
Since SwiftUI manages its UI through a separate rendering layer and does not expose elements directly in the native view hierarchy, masking is applied using modifiers. Due to the limited access the SDK has to SwiftUI’s internal structure, it cannot always detect when SwiftUI views are obscured by other elements. As a result, masks will remain active for any SwiftUI element that is part of the visible SwiftUI hierarchy, regardless of whether it is visually covered.
To address this, the app must manage view visibility explicitly and apply the masking modifier conditionally—only when the view is actually visible.
Note: SwiftUI automatically re-renders views whenever any of their bound state variables change. Avoid excessive or unnecessary state updates, as they can lead to redundant view redraws and performance degradation. However, this reactive behavior can be leveraged for masking: by using a state variable to track a view’s visibility and toggling that state, you can effectively add or remove the masking modifier when appropriate.
Below is an example that demonstrates how to conditionally apply a masking modifier to a SwiftUI view:
struct MaskedPasswordField: View {
@State private var isVisible = true
var body: some View {
if isVisible {
TextField("Enter Password", text: .constant(""))
.addMaskedView(label: "Password Field")
} else {
TextField("Enter Password", text: .constant(""))
}
}
}
WebView Masking
Masking at the WebView level can be applied using CSS selectors and element IDs, allowing the SDK to hide specific content within the WebView. Similar to SwiftUI, WebView masking is visible as long as the WebView itself is visible and part of the view hierarchy. Setting the WebView’s isHidden
property to true
will also hide its masks. However, removing the WebView from the view hierarchy does not automatically remove the masks.
WebView masking differs slightly from native view masking in that it requires explicit removal of the masking controller. When you are done masking content within a WebView, you must manually remove the GlanceWebMaskingController
. While in most cases the operating system will clean up the WebView and its associated glanceMaskContentController
when it is no longer needed, there are instances where the OS may retain the WebView for performance reasons.
Because of this, it is best practice to explicitly release the masking controller when the WebView is no longer in use. Even if the WebView has been removed from the screen, if it has not been fully deallocated, you should ensure that the glanceMaskController
calls its onDestroy()
method.
Below is the code example for properly cleaning up WebView masking:
let maskingQuerySelectors = [".mask_1", ".mask_2", "#mask_3", ".mask_4", "#hplogo"]
let maskingLabels = ["mask 1", "mask 2", "mask 3", "mask 4", "LOGO"]
glanceMaskContentController = GlanceMaskContentController(
maskingQuerySelectors.joined(separator: ", "),
labels: maskingLabels.joined(separator: ", ")
)
let config = WKWebViewConfiguration()
config.userContentController = glanceMaskContentController
maskingWebView = WKWebView(frame: self.view.bounds, configuration: config)
maskingWebView.load(URLRequest(url: URL(string: url)!))
glanceMaskContentController.setWebView(maskingWebView)
self.view.addSubview(maskingWebView)
Cleanup when done:
glanceMaskContentController.onDestroy()
Keyboard Masking (Optional)
If your app includes custom keyboards or input accessory views that display sensitive content, you should apply the same masking techniques used for regular UIKit or SwiftUI views. Depending on the implementation, use either addMaskedView
for UIKit components or the .addMaskedView()
modifier for SwiftUI.
For standard system keyboards, masking can also be applied using the following method:
Glance.maskKeyboard(true)