Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objectVersion = 70;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -2528,6 +2528,10 @@
FAF0F3CA2EA7DD0700E44E9B /* SentryANRTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
D45C039F2EDF0E4700975137 /* Public */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Public; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
63AA75971EB8AEF500D153DE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
Expand Down Expand Up @@ -4465,6 +4469,7 @@
621D9F2D2B9B030E003D94DE /* Helper */,
D44DB3802DDCCF1700174EF4 /* Persistence */,
D8F016B12B9622B7007B9AFB /* Protocol */,
D45C039F2EDF0E4700975137 /* Public */,
D856272A2A374A6800FB8062 /* Tools */,
D8B665BB2B95F5A100BD0E7B /* module.modulemap */,
FA4C32962DF7513F001D7B00 /* SentryExperimentalOptions.swift */,
Expand Down Expand Up @@ -5410,6 +5415,9 @@
);
dependencies = (
);
fileSystemSynchronizedGroups = (
D45C039F2EDF0E4700975137 /* Public */,
);
name = Sentry;
productName = "Sentry-iOS";
productReference = 63AA759B1EB8AEF500D153DE /* Sentry.framework */;
Expand Down
8 changes: 8 additions & 0 deletions Sources/Sentry/SentrySDKInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ + (SentryReplayApi *)replay
dispatch_once(&onceToken, ^{ replay = [[SentryReplayApi alloc] init]; });
return replay;
}

+ (SentryScreenshotApi *)screenshot
{
static SentryScreenshotApi *screenshot;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ screenshot = [[SentryScreenshotApi alloc] init]; });
return screenshot;
}
#endif

/** Internal, only needed for testing. */
Expand Down
37 changes: 37 additions & 0 deletions Sources/Sentry/SentryScreenshotIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# import "SentryEvent+Private.h"
# import "SentryException.h"
# import "SentryHub+Private.h"
# import "SentryInternalDefines.h"
# import "SentrySDK+Private.h"
# import "SentrySwift.h"

Expand All @@ -26,6 +27,7 @@
@interface SentryScreenshotIntegration () <SentryClientAttachmentProcessor>

@property (nonatomic, strong) SentryOptions *options;
@property (nonatomic, strong, nullable) SentryMaskingPreviewView *previewView;

@end

Expand Down Expand Up @@ -106,6 +108,41 @@ - (void)uninstall
return result;
}

- (void)showMaskPreview:(CGFloat)opacity
{
SENTRY_LOG_DEBUG(@"[Screenshot] Showing mask preview with opacity: %f", opacity);
if ([SentryDependencyContainer.sharedInstance.crashWrapper isBeingTraced] == NO) {
SENTRY_LOG_DEBUG(@"[Screenshot] No tracing is active, not showing mask preview");
return;
}

UIWindow *window =
[SentryDependencyContainer.sharedInstance.application getWindows].firstObject;
if (window == nil) {
SENTRY_LOG_WARN(@"[Screenshot] No UIWindow available to display preview");
return;
}

if (_previewView == nil) {
SentryViewScreenshotOptions *screenshotOptions = self.options.screenshot;
_previewView = [[SentryMaskingPreviewView alloc] initWithRedactOptions:screenshotOptions];
}

SentryMaskingPreviewView *previewView = _previewView;
if (previewView != nil) {
previewView.opacity = opacity;
[previewView setFrame:window.bounds];
[window addSubview:previewView];
}
}

- (void)hideMaskPreview
{
SENTRY_LOG_DEBUG(@"[Screenshot] Hiding mask preview");
[_previewView removeFromSuperview];
_previewView = nil;
}

@end

#endif // SENTRY_HAS_UIKIT
5 changes: 5 additions & 0 deletions Sources/Sentry/include/SentrySDKInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ SENTRY_NO_INIT
* API to control session replay
*/
@property (class, nonatomic, readonly) SentryReplayApi *replay;

/**
* API to control screenshot masking
*/
@property (class, nonatomic, readonly) NSObject *screenshot;
#endif

/**
Expand Down
27 changes: 27 additions & 0 deletions Sources/Sentry/include/SentryScreenshotIntegration.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@ NS_ASSUME_NONNULL_BEGIN

@interface SentryScreenshotIntegration : SentryBaseIntegration <SentryClientAttachmentProcessor>

/**
* Shows an overlay on the app to debug screenshot masking.
*
* By calling this function an overlay will appear covering the parts
* of the app that will be masked for screenshots.
* This will only work if the debugger is attached and it will
* cause some slow frames.
*
* @param opacity The opacity of the overlay.
*
* @note This method must be called from the main thread.
*
* @warning This is an experimental feature and may still have bugs.
* Do not use this in production.
*/
- (void)showMaskPreview:(CGFloat)opacity;

/**
* Removes the overlay that shows screenshot masking.
*
* @note This method must be called from the main thread.
*
* @warning This is an experimental feature and may still have bugs.
* Do not use this in production.
*/
- (void)hideMaskPreview;

@end

NS_ASSUME_NONNULL_END
Expand Down
7 changes: 7 additions & 0 deletions Sources/Swift/Helper/SentrySDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import Foundation
@objc public static var replay: SentryReplayApi {
return SentrySDKInternal.replay
}

/// API to control screenshot masking
@objc public static var screenshot: SentryScreenshotApi {
// swiftlint:disable force_cast
return SentrySDKInternal.screenshot as! SentryScreenshotApi
// swiftlint:enable force_cast
}
#endif

/// API to access Sentry logs
Expand Down
115 changes: 115 additions & 0 deletions Sources/Swift/Public/SentryScreenshotApi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#if canImport(UIKit) && !SENTRY_NO_UIKIT
#if os(iOS) || os(tvOS)

@_implementationOnly import _SentryPrivate
import Foundation
import UIKit

/**
* API to control screenshot masking.
*/
@objcMembers
public final class SentryScreenshotApi: NSObject {

/**
* Marks this view to be masked during screenshots.
*/
@objc(maskView:)
public func maskView(_ view: UIView) {
SentryRedactViewHelper.maskView(view)
}

/**
* Marks this view to not be masked during screenshot masking.
*/
@objc(unmaskView:)
public func unmaskView(_ view: UIView) {
SentryRedactViewHelper.unmaskView(view)
}

/**
* Shows an overlay on the app to debug screenshot masking.
*
* By calling this function an overlay will appear covering the parts
* of the app that will be masked for screenshots.
* This will only work if the debugger is attached and it will
* cause some slow frames.
*
* - note: This method must be called from the main thread.
*
* - warning: This is an experimental feature and may still have bugs.
* Do not use this in production.
*/
@objc(showMaskPreview)
public func showMaskPreview() {
showMaskPreview(opacity: 1.0)
}

/**
* Shows an overlay on the app to debug screenshot masking.
*
* By calling this function an overlay will appear covering the parts
* of the app that will be masked for screenshots.
* This will only work if the debugger is attached and it will
* cause some slow frames.
*
* - parameter opacity: The opacity of the overlay.
*
* - note: This method must be called from the main thread.
*
* - warning: This is an experimental feature and may still have bugs.
* Do not use this in production.
*/
@objc(showMaskPreviewWithOpacity:)
public func showMaskPreview(opacity: CGFloat) {
SentrySDKLog.debug("[Screenshot] Showing mask preview with opacity: \(opacity)")
// Use Objective-C runtime to get the integration class since it's not directly accessible from Swift
guard let integrationClass = NSClassFromString("SentryScreenshotIntegration") as? NSObject.Type else {
SentrySDKLog.debug("[Screenshot] Screenshot integration class not found")
return
}
guard let screenshotIntegration = SentrySDKInternal.currentHub()
.getInstalledIntegration(integrationClass) else {
SentrySDKLog.debug("[Screenshot] Screenshot integration not installed")
return
}

// Use performSelector to call the Objective-C method
let selector = NSSelectorFromString("showMaskPreview:")
if screenshotIntegration.responds(to: selector) {
screenshotIntegration.perform(selector, with: opacity)
}
}

/**
* Removes the overlay that shows screenshot masking.
*
* - note: This method must be called from the main thread.
*
* - warning: This is an experimental feature and may still have bugs.
* Do not use this in production.
*/
@objc(hideMaskPreview)
public func hideMaskPreview() {
SentrySDKLog.debug("[Screenshot] Hiding mask preview")
// Use Objective-C runtime to get the integration class since it's not directly accessible from Swift
guard let integrationClass = NSClassFromString("SentryScreenshotIntegration") as? NSObject.Type else {
SentrySDKLog.debug("[Screenshot] Screenshot integration class not found")
return
}
guard let screenshotIntegration = SentrySDKInternal.currentHub()
.getInstalledIntegration(integrationClass) else {
SentrySDKLog.debug("[Screenshot] Screenshot integration not installed")
return
}

// Use performSelector to call the Objective-C method
let selector = NSSelectorFromString("hideMaskPreview")
if screenshotIntegration.responds(to: selector) {
screenshotIntegration.perform(selector)
}
}
}

#endif // os(iOS) || os(tvOS)
#endif // canImport(UIKit) && !SENTRY_NO_UIKIT
Loading