SafeMediaKit – blur/report UI for Apple's on-device nudity detection SafeMediaKit, a Swift-native toolkit for Apple apps, enables privacy-preserving blur, reveal, block, and report flows for user-provided images and videos using Apple's SensitiveContentAnalysis framework on iOS 17+ and macOS 14+. The open-source package, available via Swift Package Manager, keeps all analysis on-device and prohibits transmitting flagging information off-device per Apple's developer agreement. SafeMediaKit is a Swift-native sensitive media intervention toolkit for Apple apps. It helps apps analyze user-provided images and videos before display and present privacy-preserving blur, reveal, block, and report flows. SafeMediaKit relies on Apple's public SensitiveContentAnalysis framework when available. It only works on media your app provides to it. It cannot inspect or modify content inside other apps. Background reading: What Apple's SensitiveContentAnalysis actually does and doesn't https://dev.to/sardor rakhimov/what-apples-sensitivecontentanalysis-actually-does-and-doesnt-1oe2 covers the framework's opt-in reality and where SafeMediaKit fits. SafeMediaKit is not a universal iPhone screen filter, Safari content blocker, Network Extension filter, Screen Time shield, backend moderation service, custom Core ML classifier, or telemetry SDK. It does not upload media to a server and does not include telemetry. Note for adopters: Apple's developer agreement prohibits transmitting off the device any information about whether SensitiveContentAnalysis flagged a given image or video. SafeMediaKit keeps verdicts in process; keep them local in your app too. Do not log them remotely, sync them, or attach them to report payloads. Add SafeMediaKit as a Swift Package dependency in Xcode File Add Package Dependencies… : https://github.com/SardorbekR/SafeMediaKit Or in Package.swift : dependencies: .package url: "https://github.com/SardorbekR/SafeMediaKit.git", from: "0.1.0" Add SafeMediaKit to your app target. For tests and previews, add SafeMediaKitTesting to the relevant test or demo target. SafeMediaKit targets: - iOS 17+ - macOS 14+ - Mac Catalyst 17+ The package does not claim watchOS, tvOS, or visionOS support. The host app must enable Apple's Sensitive Content Analysis capability with the entitlement: com.apple.developer.sensitivecontentanalysis.client = analysis Apple's framework may still report analysisPolicy == .disabled when the entitlement is missing, Sensitive Content Warning is off, Communication Safety is not active, or the app-specific toggle is disabled. SafeMediaKit maps that state to .unavailable .analysisPolicyDisabled and applies your configured unavailable policy. python import SafeMediaKit import SwiftUI struct MessageImage: View { let imageURL: URL let engine: SafeMediaEngine var body: some View { SafeMediaImage url: imageURL, engine: engine, context: .incomingMessage, policy: .teenMessaging, onReveal: { print "User revealed sensitive image" }, onReport: { print "User reported sensitive image" } .frame width: 240, height: 180 .clipShape RoundedRectangle cornerRadius: 16 } } You can also inject the engine through the SwiftUI environment: RootView .environment \.safeMediaEngine, engine Then use: SafeMediaImage url: imageURL, context: .incomingMessage, policy: .teenMessaging Reveal state is intentionally per-view-instance for privacy. In virtualized lists such as LazyVStack , scrolling away and back may re-blur a revealed image. To persist reveal choices, record your own message/media ID in onReveal and skip re-wrapping media the user already revealed. python import SafeMediaKit import UIKit let imageView = SafeMediaImageView imageView.configure imageURL: imageURL, engine: engine, context: .incomingMessage, policy: .teenMessaging, onReveal: { print "User revealed sensitive image" }, onReport: { print "User reported sensitive image" } SafeMediaImageView is compiled only when UIKit is available, so pure macOS builds do not expose it. Brand the intervention UI without rebuilding the flow. Pass a trailing overlay closure; it receives a SafeMediaOverlayState and replaces the built-in overlay for every non-allow state blur, block, unavailable, and load failure : SafeMediaImage url: imageURL, context: .incomingMessage, policy: .teenMessaging { state in VStack spacing: 12 { Text state.title .font .headline Text state.message .font .footnote if state.canReveal { Button "Show anyway" { state.reveal } } if state.canReport { Button "Report" { state.report } } } .padding .frame maxWidth: .infinity, maxHeight: .infinity .background .ultraThickMaterial } state.title / state.message are resolved from your SafeMediaImageConfiguration for the current decision; state.decision exposes the raw action and reason. state.reveal unblurs and fires onReveal . It is a no-op when state.canReveal is false — custom overlays cannot bypass block or no-reveal policies.- The image underneath stays blurred or hidden regardless of what the overlay draws. - To ignore the state, write { in MyOverlay } . A bare { MyOverlay } matches the onReveal callback parameter instead of the overlay slot. SafeMediaOverlayState has a public initializer, so you can preview and test custom overlays with fabricated decisions. UIKit: pass overlayProvider: to configure ... . The provided view replaces the built-in stack above the redaction blur, is pinned edge-to-edge, and is rebuilt on each reconfigure or new decision. One source note: SafeMediaImage is generic over its overlay. Call sites are unaffected, but explicit type annotations must become SafeMediaImage