<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sardor</title>
    <description>The latest articles on DEV Community by Sardor (@sardor_rakhimov).</description>
    <link>https://dev.to/sardor_rakhimov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4011764%2F71b83b4d-b441-450f-aa73-1ef8b5d35cfd.png</url>
      <title>DEV Community: Sardor</title>
      <link>https://dev.to/sardor_rakhimov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sardor_rakhimov"/>
    <language>en</language>
    <item>
      <title>What Apple's SensitiveContentAnalysis actually does (and doesn't)</title>
      <dc:creator>Sardor</dc:creator>
      <pubDate>Thu, 02 Jul 2026 20:00:24 +0000</pubDate>
      <link>https://dev.to/sardor_rakhimov/what-apples-sensitivecontentanalysis-actually-does-and-doesnt-1oe2</link>
      <guid>https://dev.to/sardor_rakhimov/what-apples-sensitivecontentanalysis-actually-does-and-doesnt-1oe2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2m3x8zomso57qfns9brj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2m3x8zomso57qfns9brj.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in iOS 17, Apple quietly shipped something remarkable: a fully on-device nudity detector, free, no ML expertise required, open to any app that adds a single entitlement. It is called SensitiveContentAnalysis. Three years later, with iOS 26 shipping and iOS 27 in beta, it remains one of the most overlooked frameworks in the SDK.&lt;/p&gt;

&lt;p&gt;After spending a few weeks building an open-source SDK around it, I think I understand why. The framework is genuinely good, but it comes with constraints Apple does not put on the label. This post covers what it really does, where it disappoints, and where it is the right tool for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SCSensitivityAnalyzer&lt;/code&gt; takes an image (file URL or &lt;code&gt;CGImage&lt;/code&gt;) or a video file and answers one question: does this contain nudity? Analysis runs entirely on device. The framework uploads nothing to anyone; whatever your report flow does with the media afterward is a separate, app-level choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SensitiveContentAnalysis&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;analyzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SCSensitivityAnalyzer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyzeImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSensitive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// blur it, block it, warn the user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app needs one entitlement: &lt;code&gt;com.apple.developer.sensitivecontentanalysis.client&lt;/code&gt; set to &lt;code&gt;analysis&lt;/code&gt;. No special approval process, just add the capability.&lt;/p&gt;

&lt;p&gt;This is the same machinery behind Communication Safety in Messages and the system-wide Sensitive Content Warning. The practical fit is app-owned media: incoming chat images, classroom uploads, profile pictures, video attachments. It is not a screen filter and cannot touch content in other apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch in the fine print
&lt;/h2&gt;

&lt;p&gt;Before analyzing anything you must check &lt;code&gt;analyzer.analysisPolicy&lt;/code&gt;. If it returns &lt;code&gt;.disabled&lt;/code&gt;, the framework will not detect anything. And here is the part that surprises every developer who tries it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For adults, detection is off unless the user opted in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;analysisPolicy&lt;/code&gt; is &lt;code&gt;.disabled&lt;/code&gt; unless your app has the entitlement and at least one user-side protection is active:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user enabled Sensitive Content Warning (Settings &amp;gt; Privacy &amp;amp; Security), which is off by default for adults&lt;/li&gt;
&lt;li&gt;Communication Safety is active through Screen Time (on by default for children under 18 with Child Accounts in Family Sharing on current Apple software)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also no way to deep-link users to that Settings pane. &lt;code&gt;UIApplication.openSettingsURLString&lt;/code&gt; opens your app's own settings page, and the undocumented &lt;code&gt;prefs:&lt;/code&gt; URL schemes are App Review bait.&lt;/p&gt;

&lt;p&gt;So if you ship a general-audience chat app and integrate this framework, the honest expectation is: for most of your adult users, analysis will be unavailable until they flip a switch they have never heard of. Your code must treat "unavailable" as a normal state with designed behavior, not an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it genuinely shines
&lt;/h2&gt;

&lt;p&gt;This sounds damning, but there are three contexts where SensitiveContentAnalysis is not just viable but the right choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-to-end encrypted chat.&lt;/strong&gt; Server-side moderation cannot inspect E2E-encrypted media. If you want automatic nudity detection in an E2E messenger without weakening encryption, it has to happen on device, and SensitiveContentAnalysis is Apple's built-in way to do that. This is exactly how Apple handles it in Messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family, teen, and classroom apps.&lt;/strong&gt; Communication Safety is on by default for children under 18 with Child Accounts in Family Sharing on current Apple software. In these apps the analyzer is actually likely to be available, and your audience is exactly who the feature exists for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Review's UGC requirements.&lt;/strong&gt; Guideline 1.2 expects user-generated-content apps to ship filtering, reporting, and blocking. On-device detection with a blur and report flow covers the filtering side with zero servers; you still need user blocking and someone who acts on reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing without explicit content
&lt;/h2&gt;

&lt;p&gt;Apple thought about the awkward part: how do you test a nudity detector without putting nudity in your repo? There is an official flow using a configuration profile and a QR code that makes the analyzer return a positive result for a harmless test image. No explicit fixtures, CI stays clean.&lt;/p&gt;

&lt;p&gt;One gotcha from my own device testing: downloading the profile is not enough. It sits inactive until you go to Settings &amp;gt; General &amp;gt; VPN &amp;amp; Device Management and tap Install. I spent a confused stretch watching the test image sail through unblurred before realizing the profile was never actually installed. If detection looks dead on a real device, check there first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new since iOS 17
&lt;/h2&gt;

&lt;p&gt;iOS 26 added &lt;code&gt;SCVideoStreamAnalyzer&lt;/code&gt; for live streams and video calls. iOS 27, in beta as I write this, adds &lt;code&gt;SCSensitivityAnalysis.detectedTypes&lt;/code&gt; with categories like &lt;code&gt;sexuallyExplicit&lt;/code&gt; and &lt;code&gt;goreOrViolence&lt;/code&gt;, so policy can finally differ by content type. Both are availability-gated, and &lt;code&gt;detectedTypes&lt;/code&gt; only compiles against the Xcode 27 SDK, so adopting them takes some &lt;code&gt;#if compiler&lt;/code&gt; care.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing layer
&lt;/h2&gt;

&lt;p&gt;For iOS 17 through iOS 26, image analysis gives you &lt;code&gt;isSensitive&lt;/code&gt;: a boolean. Everything an actual product needs sits on top of that boolean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a policy layer (what should happen for sensitive, unknown, unavailable, failed?)&lt;/li&gt;
&lt;li&gt;caching so you do not re-analyze the same file on every scroll&lt;/li&gt;
&lt;li&gt;correct handling when the user toggles the setting mid-session&lt;/li&gt;
&lt;li&gt;blur, reveal, block, and report UI that fails closed while scanning&lt;/li&gt;
&lt;li&gt;a way to test all of those states in previews and unit tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built that layer as an open-source Swift package: &lt;strong&gt;&lt;a href="https://github.com/SardorbekR/SafeMediaKit" rel="noopener noreferrer"&gt;SafeMediaKit&lt;/a&gt;&lt;/strong&gt; (MIT, iOS 17+, macOS 14+, Mac Catalyst 17+, zero dependencies, no telemetry).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SafeMediaKit&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SafeMediaEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppleSensitiveContentAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;InMemorySafeMediaVerdictCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kt"&gt;SafeMediaImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incomingMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;teenMessaging&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you scanning with a placeholder (the raw image is never handed to the view until a decision exists), blur with reveal and report, distinct copy for the unavailable state, and four preset policies from &lt;code&gt;adultMinimal&lt;/code&gt; to &lt;code&gt;classroomStrict&lt;/code&gt; that fail closed where it matters.&lt;/p&gt;

&lt;p&gt;If the bundled look does not fit your app, replace it entirely while keeping the plumbing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;SafeMediaImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incomingMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;teenMessaging&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;MyBrandedOverlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;canReveal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canReveal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;onReveal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reveal&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One deliberate constraint: &lt;code&gt;state.reveal()&lt;/code&gt; is a no-op when policy forbids revealing, and the redaction layer stays under whatever you draw. Branding cannot bypass a no-reveal policy.&lt;/p&gt;

&lt;p&gt;For testing, &lt;code&gt;SafeMediaKitTesting&lt;/code&gt; ships a mock analyzer and fixtures, so your previews can show safe, sensitive, and unavailable states without any explicit content and without the entitlement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;Not if you need guaranteed moderation for a general adult audience. Most adults never flip the switch, and no SDK changes that. But in encrypted chat and in apps for families, teens, and classrooms, you get free, private, on-device detection for exactly the users who need it. If you are building there, the framework deserves far more attention than it gets. SafeMediaKit is the layer I wish I had at the start: the two weeks of policy, caching, and fail-closed UI work that the boolean does not include.&lt;/p&gt;

&lt;p&gt;Links: &lt;a href="https://github.com/SardorbekR/SafeMediaKit" rel="noopener noreferrer"&gt;SafeMediaKit on GitHub&lt;/a&gt; · &lt;a href="https://swiftpackageindex.com/SardorbekR/SafeMediaKit" rel="noopener noreferrer"&gt;Swift Package Index&lt;/a&gt; · &lt;a href="https://developer.apple.com/documentation/sensitivecontentanalysis" rel="noopener noreferrer"&gt;Apple's SensitiveContentAnalysis docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>opensource</category>
      <category>swiftui</category>
    </item>
  </channel>
</rss>
