{"slug": "safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection", "title": "SafeMediaKit – blur/report UI for Apple's on-device nudity detection", "summary": "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.", "body_md": "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.\n\nSafeMediaKit relies on Apple's public `SensitiveContentAnalysis`\n\nframework when available. It only works on media your app provides to it. It cannot inspect or modify content inside other apps.\n\nBackground 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.\n\nSafeMediaKit 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.\n\nIt does not upload media to a server and does not include telemetry.\n\nNote for adopters: Apple's developer agreement prohibits transmitting off the device any information about whether `SensitiveContentAnalysis`\n\nflagged 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.\n\nAdd SafeMediaKit as a Swift Package dependency in Xcode (`File > Add Package Dependencies…`\n\n):\n\n```\nhttps://github.com/SardorbekR/SafeMediaKit\n```\n\nOr in `Package.swift`\n\n:\n\n```\ndependencies: [\n    .package(url: \"https://github.com/SardorbekR/SafeMediaKit.git\", from: \"0.1.0\")\n]\n```\n\nAdd `SafeMediaKit`\n\nto your app target. For tests and previews, add `SafeMediaKitTesting`\n\nto the relevant test or demo target.\n\nSafeMediaKit targets:\n\n- iOS 17+\n- macOS 14+\n- Mac Catalyst 17+\n\nThe package does not claim watchOS, tvOS, or visionOS support.\n\nThe host app must enable Apple's Sensitive Content Analysis capability with the entitlement:\n\n```\ncom.apple.developer.sensitivecontentanalysis.client = analysis\n```\n\nApple's framework may still report `analysisPolicy == .disabled`\n\nwhen 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)`\n\nand applies your configured unavailable policy.\n\n``` python\nimport SafeMediaKit\nimport SwiftUI\n\nstruct MessageImage: View {\n    let imageURL: URL\n    let engine: SafeMediaEngine\n\n    var body: some View {\n        SafeMediaImage(\n            url: imageURL,\n            engine: engine,\n            context: .incomingMessage,\n            policy: .teenMessaging,\n            onReveal: {\n                print(\"User revealed sensitive image\")\n            },\n            onReport: {\n                print(\"User reported sensitive image\")\n            }\n        )\n        .frame(width: 240, height: 180)\n        .clipShape(RoundedRectangle(cornerRadius: 16))\n    }\n}\n```\n\nYou can also inject the engine through the SwiftUI environment:\n\n```\nRootView()\n    .environment(\\.safeMediaEngine, engine)\n```\n\nThen use:\n\n```\nSafeMediaImage(\n    url: imageURL,\n    context: .incomingMessage,\n    policy: .teenMessaging\n)\n```\n\nReveal state is intentionally per-view-instance for privacy. In virtualized lists such as `LazyVStack`\n\n, scrolling away and back may re-blur a revealed image. To persist reveal choices, record your own message/media ID in `onReveal`\n\nand skip re-wrapping media the user already revealed.\n\n``` python\nimport SafeMediaKit\nimport UIKit\n\nlet imageView = SafeMediaImageView()\nimageView.configure(\n    imageURL: imageURL,\n    engine: engine,\n    context: .incomingMessage,\n    policy: .teenMessaging,\n    onReveal: {\n        print(\"User revealed sensitive image\")\n    },\n    onReport: {\n        print(\"User reported sensitive image\")\n    }\n)\n```\n\n`SafeMediaImageView`\n\nis compiled only when UIKit is available, so pure macOS builds do not expose it.\n\nBrand the intervention UI without rebuilding the flow. Pass a trailing `overlay`\n\nclosure; it receives a `SafeMediaOverlayState`\n\nand replaces the built-in overlay for every non-allow state (blur, block, unavailable, and load failure):\n\n```\nSafeMediaImage(url: imageURL, context: .incomingMessage, policy: .teenMessaging) { state in\n    VStack(spacing: 12) {\n        Text(state.title).font(.headline)\n        Text(state.message).font(.footnote)\n        if state.canReveal {\n            Button(\"Show anyway\") { state.reveal() }\n        }\n        if state.canReport {\n            Button(\"Report\") { state.report() }\n        }\n    }\n    .padding()\n    .frame(maxWidth: .infinity, maxHeight: .infinity)\n    .background(.ultraThickMaterial)\n}\n```\n\n`state.title`\n\n/`state.message`\n\nare resolved from your`SafeMediaImageConfiguration`\n\nfor the current decision;`state.decision`\n\nexposes the raw action and reason.`state.reveal()`\n\nunblurs and fires`onReveal`\n\n. It is a no-op when`state.canReveal`\n\nis false — custom overlays cannot bypass`block`\n\nor no-reveal policies.- The image underneath stays blurred or hidden regardless of what the overlay draws.\n- To ignore the state, write\n`{ _ in MyOverlay() }`\n\n. A bare`{ MyOverlay() }`\n\nmatches the`onReveal`\n\ncallback parameter instead of the overlay slot. `SafeMediaOverlayState`\n\nhas a public initializer, so you can preview and test custom overlays with fabricated decisions.\n\nUIKit: pass `overlayProvider:`\n\nto `configure(...)`\n\n. 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.\n\nOne source note: `SafeMediaImage`\n\nis generic over its overlay. Call sites are unaffected, but explicit type annotations must become `SafeMediaImage<SensitiveMediaOverlay>`\n\n(or use `some View`\n\n).\n\n``` python\nimport SafeMediaKit\n\nlet analyzer = AppleSensitiveContentAnalyzer()\nlet engine = SafeMediaEngine(\n    analyzer: analyzer,\n    cache: InMemorySafeMediaVerdictCache()\n)\n\nlet decision = await engine.evaluate(\n    .imageFile(imageURL),\n    context: .incomingMessage,\n    policy: .teenMessaging\n)\n```\n\n`SafeMediaEngine.evaluate`\n\nnever throws. Analyzer failures become `.analysisFailed`\n\ndecisions using `policy.failureAction`\n\n. That includes cancellation: if the surrounding task is cancelled during analysis, `evaluate`\n\nreturns a failure decision — callers that cancel evaluations should discard the result (the bundled views do).\n\n`AppleSensitiveContentAnalyzer`\n\nis only available on platforms where the `SensitiveContentAnalysis`\n\nframework can be imported (iOS 17+, macOS 14+, Mac Catalyst 17+).\n\nSafeMediaKit separates sensitive, unknown, unavailable, and failure behavior:\n\n| Policy | Sensitive | Unknown | Unavailable | Failure | Reveal | Report |\n|---|---|---|---|---|---|---|\n`adultMinimal` |\n`blurWithReveal` |\n`allow` |\n`allow` |\n`allow` |\nyes | no |\n`teenMessaging` |\n`blurWithReveal` |\n`blurWithReveal` |\n`blurWithReveal` |\n`blurWithReveal` |\nyes | yes |\n`childStrict` |\n`block` |\n`block` |\n`block` |\n`block` |\nno | yes |\n`classroomStrict` |\n`block` |\n`block` |\n`block` |\n`block` |\nno | yes |\n\nPreset names are UX defaults, not legal classifications, age-verification mechanisms, or safety guarantees. Host apps remain responsible for account policy, parental consent, and age-assurance requirements.\n\nBlur radius: the bundled SwiftUI/UIKit views use `SafeMediaImageConfiguration.blurRadius`\n\n. `SafeMediaPolicy.blurRadius`\n\nis carried on the policy for custom UIs that render decisions themselves.\n\nUse `SafeMediaKitTesting`\n\nto test UI states without explicit content:\n\n``` python\nimport SafeMediaKit\nimport SafeMediaKitTesting\n\nlet engine = SafeMediaEngine(\n    analyzer: MockSafeMediaAnalyzer(result: .success(.mockSensitive))\n)\n```\n\nMock fixtures include:\n\n`.mockSafe`\n\n`.mockSensitive`\n\n`.mockUnknownUnavailable`\n\nDo not commit explicit test media. Apple documents a QR-code/profile testing flow for triggering sensitive results without storing or displaying explicit content:\n\nSafeMediaKit's CI does not depend on Apple's QR profile, entitlements, user settings, or real positive detection.\n\nApple Sensitive Content Analysis is only active when the host app has the entitlement and either Sensitive Content Warning or Communication Safety is active. If neither setting is active, Apple's `analysisPolicy`\n\nis `.disabled`\n\nand SafeMediaKit cannot detect sensitive content through Apple SCA.\n\nTo enable Apple's user preference manually: Settings > Privacy & Security > Sensitive Content Warning.\n\nApps can open their own Settings page with `UIApplication.openSettingsURLString`\n\n, but SafeMediaKit does not use undocumented Settings URLs and does not claim to deep-link directly to the Sensitive Content Warning pane.\n\n`Examples/SafeMediaChatDemo`\n\ncontains a copy-paste SwiftUI demo showing safe, sensitive, and unavailable states with mock analyzers — no explicit media. See its README for setup.\n\nAll user-facing strings in the default SwiftUI and UIKit intervention UI are configurable:\n\n``` js\nlet configuration = SafeMediaImageConfiguration(\n    warningTitle: String(localized: \"This may be sensitive\"),\n    warningMessage: String(localized: \"You can choose whether to view it.\"),\n    unavailableTitle: String(localized: \"Sensitive Content Analysis is off\"),\n    unavailableMessage: String(localized: \"To use Apple sensitive-content warnings, turn on Sensitive Content Warning in Settings > Privacy & Security, or enable Communication Safety through Screen Time.\"),\n    blockedTitle: String(localized: \"Media hidden\"),\n    blockedMessage: String(localized: \"This media is hidden by the current safety policy.\"),\n    loadingTitle: String(localized: \"Scanning media\"),\n    revealButtonTitle: String(localized: \"Show\"),\n    reportButtonTitle: String(localized: \"Report\")\n)\n\nSafeMediaImage(\n    url: imageURL,\n    context: .incomingMessage,\n    policy: .teenMessaging,\n    configuration: configuration\n)\n```\n\nThe same `configuration:`\n\nparameter exists on `SafeMediaImageView.configure(...)`\n\n.\n\nSafeMediaKit processes only local media that your app passes to it. The package does not download remote URLs, upload media, log media URLs by default, or include analytics.\n\nWhen compiled with an SDK that exposes `SCSensitivityAnalysis.detectedTypes`\n\n, SafeMediaKit maps Apple's `sexuallyExplicit`\n\nand `goreOrViolence`\n\ncategories into SDK-level content types behind runtime availability checks. With older SDKs, SafeMediaKit falls back to generic sensitive-content mapping.\n\n- Video thumbnail and\n`AVPlayer`\n\npolish - Live stream analysis through\n`SCVideoStreamAnalyzer`\n\n- More category-aware policies when newer Apple APIs are broadly available\n- DocC documentation\n- Snapshot/UI tests\n\n- SensitiveContentAnalysis:\n[https://developer.apple.com/documentation/sensitivecontentanalysis](https://developer.apple.com/documentation/sensitivecontentanalysis) `SCSensitivityAnalyzer`\n\n:[https://developer.apple.com/documentation/sensitivecontentanalysis/scsensitivityanalyzer](https://developer.apple.com/documentation/sensitivecontentanalysis/scsensitivityanalyzer)- Entitlement:\n[https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.sensitivecontentanalysis.client](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.sensitivecontentanalysis.client) - Sensitive Content Warning settings:\n[https://support.apple.com/guide/iphone/receive-warnings-about-sensitive-content-iphede874992/ios](https://support.apple.com/guide/iphone/receive-warnings-about-sensitive-content-iphede874992/ios) `UIApplication.openSettingsURLString`\n\n:[https://developer.apple.com/documentation/uikit/uiapplication/opensettingsurlstring](https://developer.apple.com/documentation/uikit/uiapplication/opensettingsurlstring)- App Store Review Guidelines:\n[https://developer.apple.com/app-store/review/guidelines/](https://developer.apple.com/app-store/review/guidelines/)\n\nSee `CONTRIBUTING.md`\n\n.", "url": "https://wpnews.pro/news/safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection", "canonical_source": "https://github.com/SardorbekR/SafeMediaKit", "published_at": "2026-07-23 18:14:28+00:00", "updated_at": "2026-07-23 18:22:23.832445+00:00", "lang": "en", "topics": ["developer-tools", "ai-ethics", "ai-products"], "entities": ["SafeMediaKit", "Apple", "SensitiveContentAnalysis", "Xcode", "Swift Package Manager", "iOS", "macOS", "Mac Catalyst"], "alternates": {"html": "https://wpnews.pro/news/safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection", "markdown": "https://wpnews.pro/news/safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection.md", "text": "https://wpnews.pro/news/safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection.txt", "jsonld": "https://wpnews.pro/news/safemediakit-blur-report-ui-for-apple-s-on-device-nudity-detection.jsonld"}}