Skip to content

Commit

Permalink
fix: improve SwiftUI view tagging
Browse files Browse the repository at this point in the history
  • Loading branch information
ioannisj committed Dec 20, 2024
1 parent fc2a809 commit 7fb633f
Show file tree
Hide file tree
Showing 8 changed files with 539 additions and 204 deletions.
41 changes: 38 additions & 3 deletions PostHog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
DA5B85882CD21CBB00686389 /* AutocaptureEventProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */; };
DA979D7B2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA979D7A2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift */; };
DAB565CA2D142F8F0088F720 /* PostHogNoMaskViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB565C92D142F8F0088F720 /* PostHogNoMaskViewModifier.swift */; };
DAB565DF2D14C5660088F720 /* PostHogTagViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB565DE2D14C55C0088F720 /* PostHogTagViewModifier.swift */; };
DAC699D62CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC699D52CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift */; };
DAC699EC2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC699EB2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift */; };
DACF6D5D2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACF6D5C2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift */; };
Expand Down Expand Up @@ -405,6 +406,7 @@
DA8D37242CBEAC02005EBD27 /* PostHogExampleAutocapture.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleAutocapture.xcodeproj; path = PostHogExampleAutocapture/PostHogExampleAutocapture.xcodeproj; sourceTree = "<group>"; };
DA979D7A2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureEventTrackerSpec.swift; sourceTree = "<group>"; };
DAB565C92D142F8F0088F720 /* PostHogNoMaskViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostHogNoMaskViewModifier.swift; sourceTree = "<group>"; };
DAB565DE2D14C55C0088F720 /* PostHogTagViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogTagViewModifier.swift; sourceTree = "<group>"; };
DAC699D52CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureIntegration.swift; sourceTree = "<group>"; };
DAC699EB2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingPickerViewDelegate.swift; sourceTree = "<group>"; };
DACF6D5C2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureIntegrationSpec.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -598,10 +600,8 @@
69F518372BB2BA0100F52C14 /* PostHogSwizzler.swift */,
693E977A2C625208004B1030 /* PostHogPropertiesSanitizer.swift */,
69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */,
DAD5DD072CB6DEE70087387B /* PostHogMaskViewModifier.swift */,
DAB565C92D142F8F0088F720 /* PostHogNoMaskViewModifier.swift */,
69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */,
69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */,
DAB565D82D14C51E0088F720 /* SwiftUI */,
);
path = PostHog;
sourceTree = "<group>";
Expand Down Expand Up @@ -797,6 +797,17 @@
name = Products;
sourceTree = "<group>";
};
DAB565D82D14C51E0088F720 /* SwiftUI */ = {
isa = PBXGroup;
children = (
DAB565DE2D14C55C0088F720 /* PostHogTagViewModifier.swift */,
DAB565C92D142F8F0088F720 /* PostHogNoMaskViewModifier.swift */,
69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */,
DAD5DD072CB6DEE70087387B /* PostHogMaskViewModifier.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
};
DAD76A222D006BF7003E1A43 /* SwiftUI */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -842,6 +853,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 3AC745C9296D6FE60025C109 /* Build configuration list for PBXNativeTarget "PostHog" */;
buildPhases = (
DAB565E02D155AD10088F720 /* SwiftLint */,
3AC745B0296D6FE60025C109 /* Headers */,
3AC745B1296D6FE60025C109 /* Sources */,
3AC745B2296D6FE60025C109 /* Frameworks */,
Expand Down Expand Up @@ -1144,6 +1156,28 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
DAB565E02D155AD10088F720 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
3AA34CF3296D951A003398F4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand All @@ -1164,6 +1198,7 @@
690FF05F2AE7E2D400A0B06B /* Data+Gzip.swift in Sources */,
69EE82BE2BA9C8AA00EB9542 /* ViewLayoutTracker.swift in Sources */,
69261D1F2AD9681300232EC7 /* PostHogConsumerPayload.swift in Sources */,
DAB565DF2D14C5660088F720 /* PostHogTagViewModifier.swift in Sources */,
6955CB732C517651008EFD8D /* CGSize+Util.swift in Sources */,
69F517EA2BAC684F00F52C14 /* RRStyle.swift in Sources */,
DA0CA6F12CFF6B6300AF9500 /* UIWindow+.swift in Sources */,
Expand Down
95 changes: 0 additions & 95 deletions PostHog/PostHogMaskViewModifier.swift

This file was deleted.

88 changes: 0 additions & 88 deletions PostHog/PostHogNoMaskViewModifier.swift

This file was deleted.

53 changes: 53 additions & 0 deletions PostHog/SwiftUI/PostHogMaskViewModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// PostHogMaskViewModifier.swift
// PostHog
//
// Created by Yiannis Josephides on 09/10/2024.
//

#if os(iOS) && canImport(SwiftUI)

import SwiftUI

public extension View {
/**
Marks a SwiftUI View to be masked in PostHog session replay recordings.

Because of the nature of how we intercept SwiftUI view hierarchy (and how it maps to UIKit),
we can't always be 100% confident that a view should be masked and may accidentally mark a
sensitive view as non-sensitive instead.

Use this modifier to explicitly mask sensitive views in session replay recordings.

For example:
```swift
// This view will be masked in recordings
SensitiveDataView()
.postHogMask()

// Conditionally mask based on a flag
SensitiveDataView()
.postHogMask(shouldMask)
```

- Parameter isEnabled: Whether masking should be enabled. Defaults to true.
- Returns: A modified view that will be masked in session replay recordings when enabled
*/
func postHogMask(_ isEnabled: Bool = true) -> some View {
modifier(
PostHogTagViewModifier { uiViews in
uiViews.forEach { $0.postHogNoCapture = isEnabled }
} onRemove: { uiViews in
uiViews.forEach { $0.postHogNoCapture = false }
}
)
}
}

extension UIView {
var postHogNoCapture: Bool {
get { objc_getAssociatedObject(self, &AssociatedKeys.phNoCapture) as? Bool ?? false }
set { objc_setAssociatedObject(self, &AssociatedKeys.phNoCapture, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
#endif
54 changes: 54 additions & 0 deletions PostHog/SwiftUI/PostHogNoMaskViewModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// PostHogNoMaskViewModifier.swift
// PostHog
//
// Created by Yiannis Josephides on 09/10/2024.
//

#if os(iOS) && canImport(SwiftUI)

import SwiftUI

public extension View {
/**
Marks a SwiftUI View to be excluded from masking in PostHog session replay recordings.

There are cases where PostHog SDK will unintentionally mask some SwiftUI views.

Because of the nature of how we intercept SwiftUI view hierarchy (and how it maps to UIKit),
we can't always be 100% confident that a view should be masked. For that reason, we prefer to
take a proactive and prefer to mask views if we're not sure.

Use this modifier to prevent views from being masked in session replay recordings.

For example:
```swift
// This view may be accidentally masked by PostHog SDK
SomeSafeView()

// This custom view (and all its subviews) will not be masked in recordings
SomeSafeView()
.postHogNoMask()
```

- Returns: A modified view that will not be masked in session replay recordings
*/
func postHogNoMask() -> some View {
modifier(
PostHogTagViewModifier { uiViews in
uiViews.forEach { $0.postHogNoMask = true }
} onRemove: { uiViews in
uiViews.forEach { $0.postHogNoMask = false }
}
)
}
}

extension UIView {
var postHogNoMask: Bool {
get { objc_getAssociatedObject(self, &AssociatedKeys.phNoMask) as? Bool ?? false }
set { objc_setAssociatedObject(self, &AssociatedKeys.phNoMask, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}

#endif
Loading

0 comments on commit 7fb633f

Please sign in to comment.