<?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: swiftui</title>
    <description>The latest articles tagged 'swiftui' on DEV Community.</description>
    <link>https://dev.to/t/swiftui</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/swiftui"/>
    <language>en</language>
    <item>
      <title>Designing friction instead of blocking: what I learned building a screen-time app</title>
      <dc:creator>Martin Ma</dc:creator>
      <pubDate>Sat, 17 Oct 2026 23:45:36 +0000</pubDate>
      <link>https://dev.to/jumpingma/designing-friction-instead-of-blocking-what-i-learned-building-a-screen-time-app-4fnc</link>
      <guid>https://dev.to/jumpingma/designing-friction-instead-of-blocking-what-i-learned-building-a-screen-time-app-4fnc</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%2Fhs3ma46t3b38ozisx9vp.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%2Fhs3ma46t3b38ozisx9vp.png" alt="ChoiceField — stop scrolling without locking yourself out. iOS 17+, free to start. QR: App Store (US)" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;App Store: &lt;code&gt;https://apps.apple.com/us/app/id6769946195&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with every blocker I tried
&lt;/h2&gt;

&lt;p&gt;I've tried the usual stuff on my own phone: hard limits, one-tap locks, deleting apps. They all fail the same way. The block is absolute, so the fix is absolute too — you flip the switch off, and you're back to zero. Every attempt resets to the same starting line.&lt;/p&gt;

&lt;p&gt;My wife had the same pattern, worse: down at eleven, still awake at two, half of it short video and half of it shopping. So instead of another lock, I tried the opposite: keep access, add a small pause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rules we committed to
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never make it impossible.&lt;/strong&gt; If the user can't get back in, they'll remove the app — and then there's no signal left to work with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the exit visible.&lt;/strong&gt; Every bypass is counted and shown back to you. Hiding friction removes the feedback loop that changes behaviour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never go around the system.&lt;/strong&gt; No MDM profiles, no fake network tricks. Use the platform's own Screen Time stack.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What that looks like on iOS
&lt;/h2&gt;

&lt;p&gt;Under the hood it's the Screen Time APIs (Family Controls authorization, &lt;code&gt;DeviceActivity&lt;/code&gt; for monitoring, &lt;code&gt;ManagedSettings&lt;/code&gt; for shields). The interesting part isn't the API calls — it's the choices on top of them:&lt;/p&gt;

&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%2F6bm2yr4a8yqcaz6ifu0p.jpg" 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%2F6bm2yr4a8yqcaz6ifu0p.jpg" alt="Per-app daily allowance, set once" width="800" height="1738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-app allowance instead of one global limit.&lt;/strong&gt; A single 60-minute budget for the whole phone gets spent in ten minutes. Per-app budgets spread out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pause screen, not a wall.&lt;/strong&gt; When the allowance runs out you get a sentence and a way back in: wait a bit, or use one of a small number of unlocks you chose in advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlocks are logged, not blocked.&lt;/strong&gt; The point isn't prevention at all costs: it's seeing the pattern (three unlocks at 1am is information, not failure).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friend Freeze.&lt;/strong&gt; A trusted person can freeze apps for you for a set window. Accountability without surveillance.&lt;/li&gt;
&lt;/ul&gt;

&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%2Fd4rmugd30gz3i05g20go.jpg" 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%2Fd4rmugd30gz3i05g20go.jpg" alt="Setting a daily plan" width="800" height="1738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed, measured
&lt;/h2&gt;

&lt;p&gt;Before: phone in hand until 2am, screen time after midnight ~50 minutes. After six weeks: down to about 20 minutes, and the useful part — it didn't feel like a fight. The unlock count went from 5–6 a night to 0–1.&lt;/p&gt;

&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%2Fzbad1j6tp8du9vca5nhr.jpg" 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%2Fzbad1j6tp8du9vca5nhr.jpg" alt="Reviewing the week" width="800" height="1738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;ChoiceField is free to start on iOS 17+. No account, everything on-device.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://apps.apple.com/us/app/id6769946195&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you're building something similar and want to compare notes on the Screen Time APIs — especially the entitlement review process — happy to share what we hit.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swiftui</category>
      <category>productivity</category>
      <category>design</category>
    </item>
    <item>
      <title>Add a video editor to your iOS app (UIKit and SwiftUI)</title>
      <dc:creator>Rajesh Sharma</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:10:21 +0000</pubDate>
      <link>https://dev.to/sealobster/add-a-video-editor-to-your-ios-app-uikit-and-swiftui-21a4</link>
      <guid>https://dev.to/sealobster/add-a-video-editor-to-your-ios-app-uikit-and-swiftui-21a4</guid>
      <description>&lt;p&gt;If you ship a Swift app, you already know where Apple's help stops.&lt;br&gt;
&lt;a href="https://developer.apple.com/documentation/uikit/uivideoeditorcontroller" rel="noopener noreferrer"&gt;&lt;code&gt;UIVideoEditorController&lt;/code&gt;&lt;/a&gt; trims a clip. &lt;a href="https://developer.apple.com/documentation/avfoundation" rel="noopener noreferrer"&gt;AVFoundation&lt;/a&gt; will compose, mix and encode whatever you describe to it. Neither&lt;br&gt;
gives your users a timeline to drag clips on, a text tool, or music they can line up with a cut. This post walks through adding Nitrakit, an iOS video editor SDK, to a UIKit or SwiftUI app, and what changes when the same code runs on macOS. Every snippet is taken from the&lt;br&gt;
&lt;a href="https://docs.nitrakit.dev/platforms/ios/" rel="noopener noreferrer"&gt;iOS guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where AVFoundation ends and the editor begins
&lt;/h2&gt;

&lt;p&gt;Apple's frameworks are good at the media work: reading assets, composing tracks, exporting a file. What they leave to you is the product your users touch. A scrubbing timeline with snapping. Text they can pinch to size. Undo that covers every gesture. A preview that shows exactly what the export will contain. That surface is where in-house editors spend their time, and we've laid out the trade-off for decision-makers in &lt;a href="https://nitrakit.dev/blog/build-vs-buy-video-editor/" rel="noopener noreferrer"&gt;Build or buy an in-app video editor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nitrakit fills that gap with two &lt;code&gt;UIView&lt;/code&gt;s and a project object. You keep AVFoundation for whatever else your app already does with it, such as recording or playback elsewhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add the iOS video editor SDK as a Swift package
&lt;/h2&gt;

&lt;p&gt;There's nothing to sign up for before you install. In Xcode, open File → Add Package Dependencies, paste &lt;code&gt;https://github.com/Nitrakit/nitrakit-swift&lt;/code&gt;, and tick both products for your app target: &lt;code&gt;Nitrakit&lt;/code&gt; and &lt;code&gt;NitrakitUI&lt;/code&gt;. Teams that manage dependencies in a manifest add this instead:&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="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&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="s"&gt;"https://github.com/Nitrakit/nitrakit-swift"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="c1"&gt;// in your target:&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"NitrakitUI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"nitrakit-swift"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The floor is iOS 16 or macOS 13. Builds and test runs need no key.&lt;/p&gt;

&lt;h2&gt;
  
  
  One &lt;code&gt;Editor&lt;/code&gt; per project
&lt;/h2&gt;

&lt;p&gt;Everything the user does lands in an &lt;code&gt;Editor&lt;/code&gt;: the clips, the text, the undo stack. Give it the canvas size and frame rate you want to publish at, then place the first clip:&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fpsNum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fpsDen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pickedFileURL&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;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;magnetic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;startUs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sourceInUs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;durationUs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;durationUs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;assetId&lt;/code&gt;. It's whatever key your backend or local store already uses for that file, and it's the only way the project names its media. That small choice is why a project can later reopen on a different device.&lt;/p&gt;

&lt;h2&gt;
  
  
  UIKit: two views, your layout
&lt;/h2&gt;

&lt;p&gt;The editor arrives as a pair. &lt;code&gt;NitrakitPreviewView&lt;/code&gt; shows the video and lets people grab and move layers on it; &lt;code&gt;NitrakitTimelineView&lt;/code&gt; holds the timeline, the tool bar and its panels. Point both at the same &lt;code&gt;Editor&lt;/code&gt;:&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;NitrakitUI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NitrakitPreviewView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NitrakitTimelineView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there they behave like any view you constrain in Auto Layout or a stack view. A common arrangement puts the preview above a dock roughly 400 points high. Build them in code, not in Interface Builder.&lt;/p&gt;

&lt;p&gt;Splitting the editor this way pays off when a screen needs less than everything. A caption fixer could show the preview alone with your own transport buttons. An app with a toolbar of its own might show just the timeline. The &lt;a href="https://docs.nitrakit.dev/customization/layouts/" rel="noopener noreferrer"&gt;layouts page&lt;/a&gt; shows each variant.&lt;/p&gt;

&lt;h2&gt;
  
  
  SwiftUI: wrap once, then compose
&lt;/h2&gt;

&lt;p&gt;Both views are UIKit views, so SwiftUI needs a thin wrapper, written once. Each one goes into a &lt;code&gt;UIViewRepresentable&lt;/code&gt;, and a plain SwiftUI &lt;code&gt;View&lt;/code&gt; stacks them:&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;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Nitrakit&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;NitrakitUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EditorPreview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&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;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Editor&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&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="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;NitrakitPreviewView&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;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NitrakitPreviewView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NitrakitPreviewView&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="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EditorDock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&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;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Editor&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;@MainActor&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;EditorRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;RequestResponse&lt;/span&gt;&lt;span class="p"&gt;?)?&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&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="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;NitrakitTimelineView&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;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NitrakitTimelineView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onRequest&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NitrakitTimelineView&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="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EditorScreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&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;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Editor&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;EditorPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;EditorDock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;// your pickers: step 5&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&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;The one SwiftUI trap is identity. &lt;code&gt;body&lt;/code&gt; runs often, so the &lt;code&gt;Editor&lt;/code&gt; must live outside it: in your observable model, or in &lt;code&gt;@State&lt;/code&gt; that you fill on appear. Push &lt;code&gt;EditorScreen&lt;/code&gt; from a &lt;code&gt;NavigationStack&lt;/code&gt;, present it as a sheet, or put it in a tab; it's an ordinary view to the rest of your hierarchy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same editor on the Mac
&lt;/h2&gt;

&lt;p&gt;The package also builds for macOS 13 and later, with AppKit views. In a SwiftUI Mac target, swap &lt;code&gt;UIViewRepresentable&lt;/code&gt; for &lt;code&gt;NSViewRepresentable&lt;/code&gt; and keep the rest of the structure. One&lt;br&gt;
difference to plan for: on macOS the SDK ships no built-in choosers for fonts, colours or filters, so your app supplies every one of those screens. In return, a Mac companion app can share the editing feature, and its projects, with your iOS code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Photos access and your media library
&lt;/h2&gt;

&lt;p&gt;Nitrakit never asks the user for anything by itself. It doesn't present the photo library, a document browser, or a font list unprompted. When someone taps &lt;strong&gt;Add media&lt;/strong&gt;, the timeline view&lt;br&gt;
calls your &lt;code&gt;onRequest&lt;/code&gt; closure, and your app decides what appears. That might be [PhotosUI (&lt;a href="https://developer.apple.com/documentation/photosui" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/photosui&lt;/a&gt;), your own catalogue of stock clips, or a list of the user's uploads from your server:&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="n"&gt;dock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addMedia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addOverlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addAudio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;addSticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;presentMediaPicker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// your picker&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;startUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                            &lt;span class="c1"&gt;// your storage&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;// built-in picker&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;Because the closure is &lt;code&gt;async&lt;/code&gt; and isolated to the main actor, presenting a sheet and awaiting the choice reads top to bottom, with no delegate dance. Returning &lt;code&gt;nil&lt;/code&gt; hands fonts, colours, text styles and filters to the SDK's stock screens on iPhone and iPad, so a first build only has to wire up media. The full request list is on the&lt;br&gt;
&lt;a href="https://docs.nitrakit.dev/customization/pickers/" rel="noopener noreferrer"&gt;Pickers page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On permissions: if your flow reads from the user's photo library, your &lt;code&gt;Info.plist&lt;/code&gt; needs an &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt; string, as it would without Nitrakit. The SDK itself requests nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Drafts that follow the user
&lt;/h2&gt;

&lt;p&gt;A project serialises to JSON that refers to media by your ids, so your app decides where drafts live: on the device, in iCloud, or on your own backend.&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJson&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;reopened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reopened&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadMedia&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// from the device, else through NitrakitMedia.resolve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a reel on the iPhone, finish it on the iPad. When a draft opens somewhere its files aren't, the SDK calls one async function you set at launch, and you answer with a URL (signed or not). Clips show download progress while they arrive:&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;NitrakitMedia&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signedURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// or .file(localURL)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For autosave, register a change listener on the editor. Missing files and uploads in flight are covered in [Projects and media (&lt;a href="https://docs.nitrakit.dev/concepts/projects-and-media/" rel="noopener noreferrer"&gt;https://docs.nitrakit.dev/concepts/projects-and-media/&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the final MP4
&lt;/h2&gt;

&lt;p&gt;Rendering happens off the main thread, and the timeline stays usable while it runs. You pass a path and a listener:&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;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ExportListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fraction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* update your progress bar */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ExportResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// .done: the file is at your path. .cancelled or .failed: no file (message says why).&lt;/span&gt;
    &lt;span class="p"&gt;}&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;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exportSettingsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;shortSide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nv"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;// job.cancel()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Callbacks arrive on the export thread, hence the hop to the main queue. Once &lt;code&gt;.done&lt;/code&gt; comes back, the file is yours: hand it to &lt;code&gt;PHPhotoLibrary&lt;/code&gt;, a share sheet, or your upload queue. The &lt;a href="https://docs.nitrakit.dev/concepts/export/" rel="noopener noreferrer"&gt;Export page&lt;/a&gt; lists the settings.&lt;/p&gt;

&lt;p&gt;Users who speak on camera can also get captions timed word by word, from the speech-to-text service your app already uses. The &lt;a href="https://docs.nitrakit.dev/guides/text-and-captions/" rel="noopener noreferrer"&gt;text and captions guide&lt;/a&gt; shows how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match your app, including dark mode
&lt;/h2&gt;

&lt;p&gt;Designers will want the editor to feel native to your product, not to ours. The theme is set on the dock:&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="n"&gt;dock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;EditorTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"#101418"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"#0b0e11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"#2ee6a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Brand Sans"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;clips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"#3355ff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"#18a058"&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;The editor doesn't watch the system appearance on its own. Read the trait collection when it changes and assign the light or dark theme you've defined; that keeps the decision with your design system. Tools can be hidden or added, icons replaced and labels translated, all at run time. The [Customisation docs (&lt;a href="https://docs.nitrakit.dev/customization/" rel="noopener noreferrer"&gt;https://docs.nitrakit.dev/customization/&lt;/a&gt;) cover each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you submit to App Review
&lt;/h2&gt;

&lt;p&gt;Two things to sort out before a build goes to TestFlight for real users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The license key.&lt;/strong&gt; Development and testing run without one. For the release, the key is set once at launch, for example in the app delegate:&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;NitrakitUI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NitrakitLicense&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nk2.…"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Nitrakit:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;problems&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;It's tied to your bundle IDs, which is why it isn't a secret and can sit in source control. If a bundle ID doesn't match, &lt;code&gt;problems&lt;/code&gt; tells you. See the &lt;a href="https://docs.nitrakit.dev/concepts/licensing/" rel="noopener noreferrer"&gt;License key page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download size.&lt;/strong&gt; The SDK adds one dynamic framework, and App Store thinning ships only the device slice. The &lt;a href="https://docs.nitrakit.dev/concepts/app-size/" rel="noopener noreferrer"&gt;App size page&lt;/a&gt; has the current figures and explains how App Store encryption affects them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;The fastest check is to paste &lt;code&gt;EditorScreen&lt;/code&gt; into a scratch SwiftUI project and point it at a clip on the simulator. If you'd rather click before you compile, the &lt;a href="https://nitrakit.dev/demo/" rel="noopener noreferrer"&gt;browser demo&lt;/a&gt; runs the same editor. Ready to wire it into your real app? The &lt;a href="https://docs.nitrakit.dev/platforms/ios/" rel="noopener noreferrer"&gt;iOS guide&lt;/a&gt; goes step by step, and the&lt;br&gt;
&lt;a href="https://nitrakit.dev/ios/" rel="noopener noreferrer"&gt;iOS overview&lt;/a&gt; sums it up for whoever signs off. Your Android team gets the same editor from the &lt;a href="https://docs.nitrakit.dev/platforms/android/" rel="noopener noreferrer"&gt;Android guide&lt;/a&gt;, and a React Native app can share one codebase: [see that post (&lt;a href="https://nitrakit.dev/blog/react-native-video-editor/" rel="noopener noreferrer"&gt;https://nitrakit.dev/blog/react-native-video-editor/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Shipping date in sight? &lt;a href="https://nitrakit.dev/#access" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; about a key. Stuck on something in Xcode? The &lt;a href="https://discord.gg/4XHzFjEygk" rel="noopener noreferrer"&gt;Nitrakit Discord&lt;/a&gt; is where we answer.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>tutorial</category>
      <category>swift</category>
    </item>
    <item>
      <title>Hidden Screenshot Preview API in watchOS 27.2</title>
      <dc:creator>Saeed iPhone</dc:creator>
      <pubDate>Thu, 15 Oct 2026 10:51:58 +0000</pubDate>
      <link>https://dev.to/saeediphone/hidden-screenshot-preview-api-in-watchos-272-cc</link>
      <guid>https://dev.to/saeediphone/hidden-screenshot-preview-api-in-watchos-272-cc</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%2Fnylaxb57mknjnhla0tuu.jpg" 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%2Fnylaxb57mknjnhla0tuu.jpg" alt="Hidden Screenshot Preview API in watchOS 27.2" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recent discovery in &lt;code&gt;watchOS 27.2&lt;/code&gt; reveals a hidden feature flag related to screenshot management. Currently, &lt;code&gt;watchOS&lt;/code&gt; triggers a direct transfer to the paired iPhone via the &lt;code&gt;Bridge&lt;/code&gt; framework. The new implementation suggests a local preview UI layer, likely utilizing &lt;code&gt;SwiftUI&lt;/code&gt; or a specialized &lt;code&gt;UIKit&lt;/code&gt; overlay, allowing users to handle &lt;code&gt;UIImage&lt;/code&gt; buffers before transmission. This indicates a shift toward more granular control over system-level media capture events.&lt;/p&gt;




&lt;p&gt;منبع خبر: &lt;a href="https://www.macrumors.com/2026/09/24/watchos-27-2-screenshot-previews/" rel="noopener noreferrer"&gt;https://www.macrumors.com/2026/09/24/watchos-27-2-screenshot-previews/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>watchos</category>
      <category>applewatch</category>
      <category>swiftui</category>
      <category>ios</category>
    </item>
    <item>
      <title>Stop Using NavigationView - Mastering NavigationStack in 2026</title>
      <dc:creator>Ahtisham Hasan Khan </dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:05:49 +0000</pubDate>
      <link>https://dev.to/iahtisham/stop-using-navigationview-mastering-navigationstack-in-2026-4meh</link>
      <guid>https://dev.to/iahtisham/stop-using-navigationview-mastering-navigationstack-in-2026-4meh</guid>
      <description>&lt;p&gt;If you are still wrapping your SwiftUI views in &lt;code&gt;NavigationView&lt;/code&gt; in 2026, we need to talk.&lt;/p&gt;

&lt;p&gt;Apple soft-deprecated &lt;code&gt;NavigationView&lt;/code&gt; years ago. While it might still "work" for simple prototypes, it is a nightmare for scalable, real-world applications. If you want to handle push notifications, deep links, or complex onboarding flows, you &lt;strong&gt;must&lt;/strong&gt; use &lt;code&gt;NavigationStack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll show you how to move from "Push-and-Pray" navigation to a robust, state-driven routing system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with the Old Way
&lt;/h2&gt;

&lt;p&gt;In the early days of SwiftUI, we used &lt;code&gt;NavigationLink&lt;/code&gt; directly inside our lists. It looked innocent enough:&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="c1"&gt;// The Old Way (Don't do this anymore)&lt;/span&gt;
&lt;span class="kt"&gt;NavigationView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// ❌ Problem: DetailView is initialized immediately for EVERY row!&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go to Detail &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&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;Two things go wrong here. First, &lt;code&gt;NavigationLink(destination:)&lt;/code&gt; builds every &lt;code&gt;DetailView&lt;/code&gt; up front, even for rows nobody taps. Second, navigation lives inside the views. There is no single value that says "the user is on screen X", so you can't push a screen from a notification, restore it from a deep link, or pop back to the root from code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Way: navigation as data
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NavigationStack&lt;/code&gt; (iOS 16+) flips this around. You describe &lt;em&gt;where&lt;/em&gt; a link goes with a value, and you describe &lt;em&gt;how&lt;/em&gt; to show that value once, with &lt;code&gt;navigationDestination(for:)&lt;/code&gt;:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="c1"&gt;// ✅ Only a value is created here. DetailView is built on tap.&lt;/span&gt;
                &lt;span class="kt"&gt;NavigationLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go to Detail &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationDestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="kt"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&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;The destination view is now built lazily, and the link only carries an &lt;code&gt;Int&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Model your routes
&lt;/h2&gt;

&lt;p&gt;Real apps have more than one kind of screen. Put them in one &lt;code&gt;Hashable&lt;/code&gt; enum so every place in the app can name a destination the same way:&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;enum&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&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;h2&gt;
  
  
  Step 2: Own the path in a router
&lt;/h2&gt;

&lt;p&gt;Bind the stack to an array of routes. Whoever owns that array controls navigation:&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;Observation&lt;/span&gt;

&lt;span class="kd"&gt;@Observable&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popLast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;popToRoot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAll&lt;/span&gt;&lt;span class="p"&gt;()&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;&lt;code&gt;@Observable&lt;/code&gt; needs iOS 17. On iOS 16, make it an &lt;code&gt;ObservableObject&lt;/code&gt; with &lt;code&gt;@Published var path&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Wire the stack to the router
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;RootView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;HomeView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationDestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;profile&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;userID&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;ProfileView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&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;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;OrderView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;)&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;Any child view can now navigate without knowing about its parents:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;HomeView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;router&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Open settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&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;h2&gt;
  
  
  Step 4: Deep links and notifications for free
&lt;/h2&gt;

&lt;p&gt;Because the whole stack is just &lt;code&gt;[Route]&lt;/code&gt;, opening a screen from outside the UI means setting an array. For a URL like &lt;code&gt;myapp://order/42&lt;/code&gt;:&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onOpenURL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"order"&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastPathComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&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;The same works when a push notification arrives: parse the payload into a &lt;code&gt;Route&lt;/code&gt; and assign the path. To jump several screens deep, assign several routes, for example &lt;code&gt;[.profile(userID: "me"), .settings]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed value types: NavigationPath
&lt;/h2&gt;

&lt;p&gt;If different features push unrelated types and you don't want one big enum, use &lt;code&gt;NavigationPath&lt;/code&gt;, a type-erased path that accepts any &lt;code&gt;Hashable&lt;/code&gt; value:&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;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NavigationPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kt"&gt;NavigationStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;FeedView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationDestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;PostView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationDestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;UserView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;)&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;An enum is easier to read and test, so I start there and only reach for &lt;code&gt;NavigationPath&lt;/code&gt; when features need to stay independent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick migration checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Replace &lt;code&gt;NavigationView&lt;/code&gt; with &lt;code&gt;NavigationStack&lt;/code&gt;. For sidebar layouts on iPad and Mac, use &lt;code&gt;NavigationSplitView&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;NavigationLink(destination:)&lt;/code&gt; to &lt;code&gt;NavigationLink(value:)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add one &lt;code&gt;navigationDestination(for:)&lt;/code&gt; per value type, high in the hierarchy rather than inside lists.&lt;/li&gt;
&lt;li&gt;Move the path into a router you can reach from anywhere.&lt;/li&gt;
&lt;li&gt;Route deep links and notifications by assigning the path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building an iOS app and want routing like this from day one, &lt;a href="https://www.iahtisham.com" rel="noopener noreferrer"&gt;that's the kind of work I do&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.iahtisham.com/blog/stop-using-navigationview" rel="noopener noreferrer"&gt;iahtisham.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm Ahtisham, an independent iOS developer building native Swift apps.&lt;/strong&gt; More from me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Portfolio and client work: &lt;a href="https://www.iahtisham.com" rel="noopener noreferrer"&gt;iahtisham.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💸 &lt;a href="https://apppricekit.iahtisham.com" rel="noopener noreferrer"&gt;AppPriceKit&lt;/a&gt;: free App Store subscription prices by country&lt;/li&gt;
&lt;li&gt;🧾 &lt;a href="https://justinvoice.prismcodelabs.com" rel="noopener noreferrer"&gt;JustInvoice&lt;/a&gt;: invoice maker for iPhone&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://scanit.prismcodelabs.com" rel="noopener noreferrer"&gt;ScanIt&lt;/a&gt;: document scanner and PDF for iPhone&lt;/li&gt;
&lt;li&gt;🌙 &lt;a href="https://nur.prismcodelabs.com" rel="noopener noreferrer"&gt;NUR&lt;/a&gt;: prayer times, offline Qur'an and adhkar, free with no ads&lt;/li&gt;
&lt;li&gt;🏢 &lt;a href="https://prismcodelabs.com" rel="noopener noreferrer"&gt;PrismCode Labs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find me on &lt;a href="https://x.com/iAhtishamDev" rel="noopener noreferrer"&gt;X @iAhtishamDev&lt;/a&gt; · &lt;a href="https://github.com/iahtisham" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/ahtishamhasankhan" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://upwork.com/freelancers/iahtisham" rel="noopener noreferrer"&gt;Upwork&lt;/a&gt;&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Continuous Native iOS Widget Animation in Pure SwiftUI: Architecture &amp; App Store Review</title>
      <dc:creator>Nikita Limo</dc:creator>
      <pubDate>Fri, 02 Oct 2026 18:47:40 +0000</pubDate>
      <link>https://dev.to/limooonik/continuous-native-ios-widget-animation-in-pure-swiftui-architecture-app-store-review-4fek</link>
      <guid>https://dev.to/limooonik/continuous-native-ios-widget-animation-in-pure-swiftui-architecture-app-store-review-4fek</guid>
      <description>&lt;p&gt;Within the iOS developer community, a deep-rooted belief exists that creating continuous, fluid animation on home screen widgets is technically impossible due to Apple's strict refresh limits and resource allocation policies. Most attempts to realize such a UI boil down to using heavy image sequences or wrappers for video files, which inevitably leads to RAM overload, battery drain, and swift rejection during App Store Review.&lt;/p&gt;

&lt;p&gt;However, this "technical impossibility" is a myth born from the wrong approach. Continuous animation is absolutely possible, provided the widget architecture is built exclusively on native, mathematically deterministic SwiftUI mechanisms, without attempting to "bypass" the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Apple Allow Widget Animation? Official WidgetKit Documentation
&lt;/h2&gt;

&lt;p&gt;The official Apple documentation (in the &lt;a href="https://developer.apple.com/documentation/widgetkit/animating-data-updates-in-widgets-and-live-activities" rel="noopener noreferrer"&gt;Animating data updates in widgets and Live Activities&lt;/a&gt; section) explicitly states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Widgets and Live Activities support all built-in SwiftUI transitions and animations."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system does not prohibit animation. It prevents the inefficient use of resources. Passing App Store Review goes smoothly: since the developer uses exclusively Apple's native ecosystem, it would be completely illogical for moderators to reject an application that works flawlessly and optimally on their own built-in technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Without GIF or Video: Bézier Curves (Path) and TimelineProvider
&lt;/h2&gt;

&lt;p&gt;Instead of forcing the &lt;code&gt;TimelineProvider&lt;/code&gt; to manually refresh the widget dozens of times per second, the approach is built on orchestrating three core native components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic Timeline:&lt;/strong&gt; &lt;code&gt;TimelineProvider&lt;/code&gt; is used strictly for its intended purpose—it delivers key anchor states at permitted intervals. It does not generate the animation itself; it sets the schedule of states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector Geometry (Bézier Curves):&lt;/strong&gt; All graphical elements are drawn via &lt;code&gt;Path&lt;/code&gt;. Complex objects are not images, but pure mathematics:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCurve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
              &lt;span class="nv"&gt;control1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;control1X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;control1Y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
              &lt;span class="nv"&gt;control2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;control2X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;control2Y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native Interpolation:&lt;/strong&gt; The heavy lifting is done by the standard SwiftUI modifier. When the timeline switches a state, the system hardware (via CoreGraphics) smoothly renders the morphing:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dampingFraction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Developers Give Up, or The Complexity of Native Vector Morphing
&lt;/h2&gt;

&lt;p&gt;The reason for the lack of such solutions in the market lies in the extremely high barrier to entry. Testing the fundamental principles of vector morphing inside &lt;code&gt;WidgetKit&lt;/code&gt; and building a stable architecture takes months of deep R&amp;amp;D. However, now that the basic principle is clear and the core is built, creating a new complex animation takes no more time than assembling a standard file in popular editors like Lottie or Rive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Pure SwiftUI: Dynamic Color, 100 KB Size, and Zero Battery Drain
&lt;/h2&gt;

&lt;p&gt;Utilizing pure code unlocks capabilities fundamentally unavailable to pre-rendered files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Absolute Customization:&lt;/strong&gt; In real-time, you can modify everything just like in the source vector file: shape, line thickness (&lt;code&gt;stroke&lt;/code&gt;), transparency (&lt;code&gt;.opacity&lt;/code&gt;), blur (&lt;code&gt;.blur&lt;/code&gt;), scale (&lt;code&gt;.scaleEffect&lt;/code&gt;), rotation (&lt;code&gt;.rotationEffect&lt;/code&gt;, &lt;code&gt;.rotation3DEffect&lt;/code&gt;), shadows (&lt;code&gt;.shadow&lt;/code&gt;), and the color of any element, supporting the full spectrum of color models (RGB, HSB).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-layering and Reactivity:&lt;/strong&gt; A single widget can run several independent animations concurrently (for example, a continuous looped background animation and simultaneously an instant reaction to a "tap"). Animations easily bind to events: time of day, achieving a new level in a game, a system reminder triggering, or toggling a native switch right on the widget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extreme Optimization (50-100 KB):&lt;/strong&gt; Once compiled, a highly complex animation weighs only 50-100 KB. An equivalent result in a video format would consume megabytes of memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integration with Flutter, React Native, and Support for 4x6 Widgets in iOS 27
&lt;/h2&gt;

&lt;p&gt;Because the architecture is based on foundational Swift, it possesses 100% versatility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration:&lt;/strong&gt; The native Widget Extension is written in Swift but seamlessly integrates with any cross-platform projects on Flutter, React Native, and other frameworks (via App Groups).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions:&lt;/strong&gt; The graphics fit perfectly into any size—from the classic Small size up to the massive 4x6 widgets introduced in iOS 27.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem:&lt;/strong&gt; Code written once requires no additional adaptations and runs natively on iPhone, iPad, macOS desktop widgets, in the Smart Stack on Apple Watch, and also supports 120Hz ProMotion on Pro device displays.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Proof of Concept: Offline Mode and Xcode Profiling
&lt;/h2&gt;

&lt;p&gt;Any developer can verify the absence of "hidden hacks" or server-side computations (convincing someone that we do not use third-party engines like Lottie/Rive is pointless, as iOS fundamentally blocks them at the widget level) by observing the following criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Full Autonomy (Offline mode):&lt;/strong&gt; When network access is disabled (Airplane mode), continuous animation and dynamic color changes continue to work without interruptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infinite Zoom:&lt;/strong&gt; When the graphics are heavily zoomed in, the quality remains crystal clear, without pixelation effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Audit and Resource Profiling:&lt;/strong&gt; The source code of the open XCFramework can be downloaded from &lt;a href="https://github.com/Limooonik" rel="noopener noreferrer"&gt;my GitHub repository&lt;/a&gt; and run through any static code analysis systems or &lt;code&gt;Xcode Instruments&lt;/code&gt;. You will personally confirm that it exclusively uses Apple's native frameworks, exhibits no memory leaks, and places minimal load on the CPU.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Production Examples
&lt;/h2&gt;

&lt;p&gt;Evaluate the technology in production through published apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lim: Relaxing Animated Widgets:&lt;/strong&gt; Interactive, aesthetic continuous animations. &lt;a href="https://limooonik.github.io/" rel="noopener noreferrer"&gt;View Project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clock Widgets Home Screen:&lt;/strong&gt; Continuous native motion utility widgets. &lt;a href="https://apps.apple.com/us/app/clock-widgets-homescreen/id6755961385" rel="noopener noreferrer"&gt;View on App Store&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deep Dive into Architecture &amp;amp; B2B Services&lt;br&gt;
This article is part of our comprehensive documentation on native iOS motion.&lt;br&gt;
To see how this is built under the hood using Bézier curves and TimelineProvider, read the full &lt;a href="https://limooonik.github.io/architecture.html" rel="noopener noreferrer"&gt;Architecture &amp;amp; App Store Review guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking to implement continuous animation in your product without failing App Review? We provide production-ready XCFrameworks. Explore our&lt;a href="https://limooonik.github.io/custom-animation.html" rel="noopener noreferrer"&gt; Custom Engineering Services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>widgetkit</category>
      <category>widget</category>
      <category>ios</category>
      <category>swiftui</category>
    </item>
    <item>
      <title>How to stop your AI coding agent from inventing SwiftUI UI</title>
      <dc:creator>Daniel S</dc:creator>
      <pubDate>Sat, 26 Sep 2026 05:39:07 +0000</pubDate>
      <link>https://dev.to/nerd_snipe_dev/how-to-stop-your-ai-coding-agent-from-inventing-swiftui-ui-1km</link>
      <guid>https://dev.to/nerd_snipe_dev/how-to-stop-your-ai-coding-agent-from-inventing-swiftui-ui-1km</guid>
      <description>&lt;p&gt;Ask a coding agent for a few SwiftUI screens in separate sessions and you get a separate set of decisions each time. The corner radius is 10 on one and 12 on another. The blue is &lt;code&gt;.blue&lt;/code&gt; here and &lt;code&gt;Color(red: 0.2, green: 0.4, blue: 0.9)&lt;/code&gt; there. Each screen compiles and looks fine alone. Side by side they read as different apps, and each fix is another prompt to pay for.&lt;/p&gt;

&lt;p&gt;The agent has nothing in the project that says what your app looks like, so it guesses. The setup below gives it something to read and something to run into when it strays. It uses &lt;a href="https://nerdsnipe.cc/opensource-projects/design-foundation" rel="noopener noreferrer"&gt;DesignFoundation&lt;/a&gt;, a free MIT-licensed SwiftUI package I work on, but the three parts (a token theme, instruction files, a lint rule) apply to any design system. The longer argument for why this works is in &lt;a href="https://nerdsnipe.cc/blog/stop-arguing-with-your-ai-about-swiftui-design-give-it-a-system" rel="noopener noreferrer"&gt;the companion post on the NerdSnipe blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: put the theme in the environment
&lt;/h2&gt;

&lt;p&gt;Add the package. It needs Xcode 16 and targets iOS 18, macOS 15, and visionOS 2.&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="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&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="s"&gt;"https://github.com/NerdSnipe-Inc/design-foundation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&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;Then set a preset once at the app root:&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;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;DesignFoundation&lt;/span&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dfThemePreset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&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;Five presets ship: &lt;code&gt;.slate&lt;/code&gt;, &lt;code&gt;.aurora&lt;/code&gt;, &lt;code&gt;.copper&lt;/code&gt;, &lt;code&gt;.sage&lt;/code&gt;, and &lt;code&gt;.garnet&lt;/code&gt;. Each has a light and a dark theme and follows the system setting. Every DesignFoundation component below that modifier reads colors, spacing, radii, typography, and shadows from the theme, so &lt;code&gt;DFButton("Save") { }&lt;/code&gt; picks up the preset without any styling in the call.&lt;/p&gt;

&lt;p&gt;Your own views read the same tokens through the environment:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;ProfileChip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dfTheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;theme&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;HStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textPrimary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;surfaceElevated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Capsule&lt;/span&gt;&lt;span class="p"&gt;())&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;To change one thing, copy a theme, edit it, and inject it:&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="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;custom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DFTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sageLight&lt;/span&gt;
&lt;span class="n"&gt;custom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purple&lt;/span&gt;

&lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dfTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: give the agent the rules
&lt;/h2&gt;

&lt;p&gt;The repo ships instructions for the agents that will write against it: &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;, a Cursor rule at &lt;code&gt;.cursor/rules/design-foundation.mdc&lt;/code&gt; with &lt;code&gt;alwaysApply: true&lt;/code&gt;, and &lt;code&gt;docs/llms.txt&lt;/code&gt;. The first rule in all of them is to never build UI the package already provides. Below that they list real signatures, mostly the places generic SwiftUI habits break. Three examples:&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;DFCheckbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$agreed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"I agree"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;// label: is a keyword argument&lt;/span&gt;
&lt;span class="kt"&gt;DFButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// destructive is a role; no icon: or isLoading:&lt;/span&gt;
&lt;span class="kt"&gt;DFText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="c1"&gt;// scale:, not style:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SwiftPM checks out the whole repository, so these files are already on your disk once the package resolves. In a package-manifest project they sit in &lt;code&gt;.build/checkouts/design-foundation/&lt;/code&gt;. In Xcode they are under DerivedData in &lt;code&gt;SourcePackages/checkouts/design-foundation/&lt;/code&gt;. I confirmed both &lt;code&gt;AGENTS.md&lt;/code&gt; and the Cursor rule are present in a fresh checkout of the current release.&lt;/p&gt;

&lt;p&gt;How you wire them into your own app is up to you, since the package doesn't prescribe it. Two options I'd consider. You can copy the file your agent reads into your app repo, which is simple and goes stale when you upgrade the package. Or you can point your app's own instruction file at the checked-out copy, so it updates with the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# UI rules&lt;/span&gt;
Before writing any SwiftUI, read the DesignFoundation instructions at
.build/checkouts/design-foundation/AGENTS.md and follow them.
Never build a component that DesignFoundation provides.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That block is my example, not something from the repo. Adjust the path for your setup.&lt;/p&gt;

&lt;p&gt;The repo also has a workflow, &lt;code&gt;doc-snippets.yml&lt;/code&gt;, that compiles every Swift block in those files against the real package on every change, so the instructions can't drift from the API unnoticed. I compiled the snippets in this post against the current release for the same reason.&lt;/p&gt;

&lt;p&gt;The cost is context. &lt;code&gt;AGENTS.md&lt;/code&gt; is about 400 lines, and the agent loads it every session. If you are generating one small screen, that overhead can outweigh the saving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: add the lint rules
&lt;/h2&gt;

&lt;p&gt;Instructions can't stop a raw &lt;code&gt;Color(red:green:blue:)&lt;/code&gt; from landing in a pull request, and neither can the Swift compiler. The package includes &lt;code&gt;Tooling/swiftlint-design-foundation-tokens.yml&lt;/code&gt;, a block of SwiftLint &lt;code&gt;custom_rules&lt;/code&gt; with four rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;design_foundation_no_raw_color&lt;/code&gt; for &lt;code&gt;Color(red:...)&lt;/code&gt;, &lt;code&gt;Color(hue:...)&lt;/code&gt;, and similar&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design_foundation_no_named_color_literal&lt;/code&gt; for &lt;code&gt;Color.red&lt;/code&gt;, &lt;code&gt;Color.gray&lt;/code&gt;, and the rest&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design_foundation_no_raw_font&lt;/code&gt; for &lt;code&gt;.font(.system(...))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design_foundation_no_hardcoded_corner_radius&lt;/code&gt; for &lt;code&gt;cornerRadius: 10&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Merge the block into your &lt;code&gt;.swiftlint.yml&lt;/code&gt;, or pull it in with &lt;code&gt;!include&lt;/code&gt;. Each rule has a message that names the token to use instead, for example "A literal corner-radius value won't track a theme's radius scale, use theme.radius.sm/md/lg/xl instead." An agent that runs SwiftLint reads that message and knows the fix.&lt;/p&gt;

&lt;p&gt;The rules are regex-based and set to &lt;code&gt;warning&lt;/code&gt;, so expect the occasional false positive, like a preview fixture or a third-party API that needs a plain &lt;code&gt;Color&lt;/code&gt;. Silence those on the line with &lt;code&gt;// swiftlint:disable:next design_foundation_no_raw_color&lt;/code&gt;. In CI, the warnings show up on the pull request.&lt;/p&gt;

&lt;p&gt;You still review every diff, since none of this makes the agent's logic correct. The components don't need &lt;code&gt;#if os()&lt;/code&gt;, but your own scene declarations and any platform API the package doesn't wrap still do. The token saving is less generated code, and no benchmark backs a number for it: the docs site's "Foundation Way" comparison page rests on its own line-count samples and a rough 35-characters-per-line estimate.&lt;/p&gt;

&lt;p&gt;If you keep asking the agent for the same whole screens (sign-in, analytics, settings) and rewiring them each time, there's a paid add-on on the same tokens, &lt;a href="https://nerdsnipe.cc/design-foundation-pro" rel="noopener noreferrer"&gt;DesignFoundationPro&lt;/a&gt;, with 55 finished screens and 18 navigation shells. The free package works without it.&lt;/p&gt;

&lt;p&gt;The source is on &lt;a href="https://github.com/NerdSnipe-Inc/design-foundation" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and the &lt;a href="https://nerdsnipe-inc.github.io/design-foundation/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; have the component reference. Build one real screen in a sandbox app, then run SwiftLint over it.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>swift</category>
      <category>devops</category>
    </item>
    <item>
      <title>Turn any iOS app into a full HTTPS server</title>
      <dc:creator>Danilo</dc:creator>
      <pubDate>Thu, 24 Sep 2026 17:37:20 +0000</pubDate>
      <link>https://dev.to/dapri69/turn-any-ios-app-into-a-full-https-server-5bk9</link>
      <guid>https://dev.to/dapri69/turn-any-ios-app-into-a-full-https-server-5bk9</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%2F96ayzul57i1rv39banyx.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%2F96ayzul57i1rv39banyx.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/priore/SwiftCoreWeb" rel="noopener noreferrer"&gt;https://github.com/priore/SwiftCoreWeb&lt;/a&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>websocket</category>
      <category>webserver</category>
    </item>
    <item>
      <title>Design Handoff That Survives Contact With SwiftUI, Compose and React Native</title>
      <dc:creator>James Sanderson</dc:creator>
      <pubDate>Wed, 16 Sep 2026 14:19:19 +0000</pubDate>
      <link>https://dev.to/jam-techcirkle/design-handoff-that-survives-contact-with-swiftui-compose-and-react-native-37kh</link>
      <guid>https://dev.to/jam-techcirkle/design-handoff-that-survives-contact-with-swiftui-compose-and-react-native-37kh</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%2Fu41eq53j7u4s9p2rbtny.jpg" 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%2Fu41eq53j7u4s9p2rbtny.jpg" alt="Mobile app wireframes and colour palettes laid out during a design review" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You receive a Figma link. The screens are clean, the spacing is consistent, and the prototype clicks through nicely. Then you start building and the questions arrive. What does this list do with 2,000 rows? What happens when the display name is 40 characters? Where is the offline state? Does this sheet behave the same on a foldable?&lt;/p&gt;

&lt;p&gt;The design was not wrong. It was incomplete in exactly the places that cost engineering time. This post is a developer-side view of what a handoff should contain so that it maps to real code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tokens are the contract, not the artwork
&lt;/h2&gt;

&lt;p&gt;If the design system only exists as Figma styles, every engineer re-derives it by eye, and drift starts on day one. The fix is a token source of truth that generates platform constants.&lt;/p&gt;

&lt;p&gt;A minimal token file might look like this. &lt;em&gt;Illustrative only; your naming scheme will differ.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#FFFFFF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"raised"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#F5F6F8"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#14161A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"secondary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#5B6270"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#2F5BEA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"xs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"md"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"radius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated for SwiftUI. &lt;em&gt;Illustrative output.&lt;/em&gt;&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;enum&lt;/span&gt; &lt;span class="kt"&gt;Tokens&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;surfaceDefault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"surfaceDefault"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;textPrimary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"textPrimary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;accentDefault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"accentDefault"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Space&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&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;And for Jetpack Compose. &lt;em&gt;Illustrative output.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Tokens&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SurfaceDefault&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;TextPrimary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF14161A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;AccentDefault&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2F5BEA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SpaceSm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SpaceMd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For React Native, the same source typically emits a typed theme object consumed by a &lt;code&gt;StyleSheet&lt;/code&gt; or your styling library of choice.&lt;/p&gt;

&lt;p&gt;A few rules make this hold up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic names, not raw values.&lt;/strong&gt; &lt;code&gt;text.secondary&lt;/code&gt;, not &lt;code&gt;grey500&lt;/code&gt;. Dark mode and theming then become a mapping change rather than a refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colours reference asset catalogues or resources&lt;/strong&gt; so light and dark variants live in platform-native places.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typography tokens map to scalable text styles&lt;/strong&gt;, so Dynamic Type on iOS and font scaling on Android keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One pipeline, versioned.&lt;/strong&gt; Token changes arrive as a reviewable diff, not a Slack message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask your design partner whether their components correspond one to one with code components. If the Figma "Card" has six variants and your code has two, someone will guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document every state, or developers will invent them
&lt;/h2&gt;

&lt;p&gt;Happy-path screens are maybe a third of the UI. The rest is states. A usable handoff specifies, per screen or component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty.&lt;/strong&gt; First run, no results, filtered to nothing. These are different states with different copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loading.&lt;/strong&gt; Skeleton or spinner, and what is interactive while loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error.&lt;/strong&gt; Recoverable versus fatal, retry behaviour, and message tone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; Cached content shown with a staleness cue, queued actions, and what is disabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial data.&lt;/strong&gt; Missing avatar, missing price, one field failed to load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One cheap way to enforce this in code is to model the states explicitly, so an undesigned state is a compile-time gap rather than a runtime surprise. &lt;em&gt;Illustrative Kotlin.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrdersUiState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrdersUiState&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Empty&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrdersUiState&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isStale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrdersUiState&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrdersUiState&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a &lt;code&gt;when&lt;/code&gt; over that type has a branch with no corresponding design, you have found a handoff gap before shipping it.&lt;/p&gt;

&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%2Fywd3dtxpvy1k51bkxwzf.jpg" 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%2Fywd3dtxpvy1k51bkxwzf.jpg" alt="Designer and developer notes on mobile interface sketches" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stress cases belong in the spec
&lt;/h2&gt;

&lt;p&gt;These are the questions that turn a two-day ticket into a two-week one when nobody answered them up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long strings.&lt;/strong&gt; Names, addresses and translated copy that are two or three times the English length. Truncate, wrap or scale? Where does the ellipsis go?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large lists.&lt;/strong&gt; A list with 2,000 items needs virtualisation, stable keys, pagination or incremental loading, and a decision about section headers and scroll position restoration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Largest accessibility text sizes.&lt;/strong&gt; Does the layout reflow, or does the horizontal row need to stack?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foldables and tablets.&lt;/strong&gt; Two-pane layouts, what happens on fold or unfold mid-task, and whether state survives configuration changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-end devices.&lt;/strong&gt; Which animations are essential, and which can be reduced when the device or the user's reduced-motion setting asks for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good mobile app design agency answers these in annotations. A great one has already prototyped the awkward ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design QA on real builds, not screenshots
&lt;/h2&gt;

&lt;p&gt;Comparing a screenshot to a Figma frame catches spacing bugs. It does not catch jank, gesture conflicts, keyboard overlap or a transition that feels wrong at 60 frames per second on a mid-range phone.&lt;/p&gt;

&lt;p&gt;A workable rhythm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every build increment goes to designers on physical devices&lt;/strong&gt;, both platforms, including at least one lower-spec Android phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Designers file issues against the build&lt;/strong&gt;, tagged by severity, with a screen recording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineers flag expensive interactions early&lt;/strong&gt;, before polish, so the design can change while change is cheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token or component changes go through the same review&lt;/strong&gt; as code changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The teams that do this well treat designers as part of the build loop, not a phase that ended. That is also the practical argument for partners who handle design and &lt;a href="https://techcirkle.com/development/mobile-app-development" rel="noopener noreferrer"&gt;mobile app development&lt;/a&gt; from one backlog: questions get answered in hours, not after a weekly sync.&lt;/p&gt;

&lt;p&gt;For the buyer-side view of evaluating a design partner, including how to spot a genuine mobile specialist, see the full guide: &lt;a href="https://techcirkle.com/blog/mobile-app-design-agency" rel="noopener noreferrer"&gt;Mobile App Design Agency: How to Choose One That Designs for Engineering, Retention and AI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the minimum a mobile design handoff should include?
&lt;/h3&gt;

&lt;p&gt;A versioned token source, components that match code components, annotated screens covering empty, loading, error and offline states, motion specs, accessibility notes, and a commitment to review real builds during development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should design tokens live in Figma or in the repository?
&lt;/h3&gt;

&lt;p&gt;Author them wherever designers work best, but export them to a versioned file in the repository and generate platform constants from it. The repository copy is what builds depend on, so changes should be reviewable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do tokens support dark mode on iOS and Android?
&lt;/h3&gt;

&lt;p&gt;Use semantic token names and map them to asset catalogue colours on iOS and resource-qualified colours or theme values on Android. Switching appearance then changes the mapping rather than every component.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should very long lists be specified?
&lt;/h3&gt;

&lt;p&gt;The design should state loading behaviour, placeholder rows, section headers, empty and error states, and scroll restoration. Engineering then chooses virtualisation and pagination strategies that match that behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does design QA on real builds catch that screenshots miss?
&lt;/h3&gt;

&lt;p&gt;Frame drops, gesture conflicts with system navigation, keyboard overlap, touch target problems, awkward transitions and layouts that break at large text sizes or on foldable screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can cross-platform frameworks share one token set?
&lt;/h3&gt;

&lt;p&gt;Yes. A single token source can generate a theme object for React Native or Flutter alongside native SwiftUI and Compose constants, with platform-specific overrides where conventions differ.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>swiftui</category>
      <category>android</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>iOS 27 Just Redefined Swift: 5 Game-Changing Features Every iOS Developer Must Master</title>
      <dc:creator>JIBIN MATHEW</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:23:03 +0000</pubDate>
      <link>https://dev.to/jibinmathewjose/ios-27-just-redefined-swift-5-game-changing-features-every-ios-developer-must-master-44jn</link>
      <guid>https://dev.to/jibinmathewjose/ios-27-just-redefined-swift-5-game-changing-features-every-ios-developer-must-master-44jn</guid>
      <description>&lt;p&gt;If you’ve been building iOS applications over the last few years, you are already familiar with the shift toward declarative UI and structured concurrency. But &lt;strong&gt;iOS 27&lt;/strong&gt; marks a watershed moment for the Apple developer ecosystem. &lt;/p&gt;

&lt;p&gt;Just as iOS 7 dismantled skeuomorphism and iOS 13 brought SwiftUI into our daily lives, iOS 27 completely rewrites the rules for how Swift interfaces with system hardware, on-device neural engines, and spatial display pipelines.&lt;/p&gt;

&lt;p&gt;If your team is still writing Swift patterns from 2022, your code isn't just accumulating tech debt—&lt;strong&gt;it’s falling behind the platform's core performance optimizations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are the 5 biggest Swift and platform changes in iOS 27 that every iOS engineer, tech lead, and architect needs to master today.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Zero-Data-Race Concurrency: Region-Based Isolation is Mandatory
&lt;/h2&gt;

&lt;p&gt;In previous Swift releases, handling data races across thread boundaries required manual actor isolation and constant annotations. With iOS 27 and the latest Swift toolchain, &lt;strong&gt;Region-Based Isolation&lt;/strong&gt; is fully enforced at compile time.&lt;/p&gt;

&lt;p&gt;Swift no longer just checks if a type is &lt;code&gt;Sendable&lt;/code&gt;; the compiler tracks the memory region of mutable values across async execution boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Comparison: Transferring Non-Sendable State
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (iOS 25/26 Workaround):&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
swift
// Needed verbose @unchecked Sendable or heavy locks
final class UserSession {
    var token: String
    init(token: String) { self.token = token }
}

func syncSession(_ session: UserSession) async {
    // Danger: Potential data race if mutated concurrently on another thread
    await Task.detached {
        print("Syncing: \(session.token)")
    }.value
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>swiftui</category>
      <category>apple</category>
    </item>
    <item>
      <title>What a small macOS tracker taught us about privacy boundaries</title>
      <dc:creator>Klik</dc:creator>
      <pubDate>Tue, 15 Sep 2026 02:17:44 +0000</pubDate>
      <link>https://dev.to/hiklikai/what-a-small-macos-tracker-taught-us-about-privacy-boundaries-4ja9</link>
      <guid>https://dev.to/hiklikai/what-a-small-macos-tracker-taught-us-about-privacy-boundaries-4ja9</guid>
      <description>&lt;p&gt;Pacering is a small open-source macOS productivity tracker from our wider R&amp;amp;D shelf. The interesting part is not another dashboard; it is where the product draws its data boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the measurement loop understandable
&lt;/h2&gt;

&lt;p&gt;The app is built with SwiftUI and Combine. It uses &lt;code&gt;NSWorkspace&lt;/code&gt; to observe active applications, lets the user decide which apps count as work, and keeps the core tracking state in &lt;code&gt;UserDefaults&lt;/code&gt;. UI-driven changes are debounced rather than written on every keystroke.&lt;/p&gt;

&lt;p&gt;That is a modest architecture, but modest can be good: a user can understand what is measured, choose the relevant applications, set a daily goal, and inspect the resulting history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional intelligence is a separate boundary
&lt;/h2&gt;

&lt;p&gt;The repository also contains an optional GLM-backed analysis path. That distinction matters. “The tracker is local” should not be allowed to blur into “every optional analysis path is local.” Product copy, permissions, and implementation should all make that boundary legible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance work is product work
&lt;/h2&gt;

&lt;p&gt;The current code and README describe debounced persistence, cached window-title lookups, and limiting more expensive inspection to browser applications. Those are small choices, but together they reduce how intrusive a background productivity tool feels.&lt;/p&gt;

&lt;p&gt;The broader lesson: privacy is easier to reason about when collection, local persistence, and optional network intelligence are separate decisions—not one vague toggle.&lt;/p&gt;

&lt;p&gt;Source and implementation: &lt;a href="https://github.com/chengyixu/pacering" rel="noopener noreferrer"&gt;https://github.com/chengyixu/pacering&lt;/a&gt;&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>macos</category>
      <category>privacy</category>
      <category>opensource</category>
    </item>
    <item>
      <title>iPhone Duo: What Apple's 'Boring' Keynote Actually Means for iOS Developers</title>
      <dc:creator>Kirill Lukyanov</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:28:34 +0000</pubDate>
      <link>https://dev.to/klukyanov/iphone-duo-what-apples-boring-keynote-actually-means-for-ios-developers-20dp</link>
      <guid>https://dev.to/klukyanov/iphone-duo-what-apples-boring-keynote-actually-means-for-ios-developers-20dp</guid>
      <description>&lt;p&gt;Apple's September keynote felt like "nothing interesting": a slightly faster Pro, a slightly pricier Pro, new watches, new earbuds, and a foldable that looked almost exactly like the leaks. For buyers, that's a fair summary. For iOS developers, it's the opposite — the evening quietly delivered the biggest layout change since the iPhone X notch, and the clock on it runs out on October 23.&lt;/p&gt;

&lt;p&gt;The day before the event I published 13 predictions with probabilities. Here's how they did, what was actually shown, and — the part I really care about — what the iPhone Duo's inner display means for your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scorecard: 11 out of 13
&lt;/h2&gt;

&lt;p&gt;Probabilities were set on the morning of September 9, before the stream, and haven't been touched since. Anything below 50% means I was betting &lt;em&gt;against&lt;/em&gt; the event.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prediction&lt;/th&gt;
&lt;th&gt;My odds&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A foldable iPhone is shown&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;Shown — iPhone Duo&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;It's called iPhone Ultra&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;td&gt;It's called iPhone Duo&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Foldable starts at $2,000&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;td&gt;$1,999 for 256 GB&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touch ID instead of Face ID on the foldable&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;Touch ID in the side button&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No telephoto on the foldable&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;Main 48 MP + ultra wide; 2x is a crop of the main sensor&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No base iPhone 18&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;Moved to spring 2027&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A20 Pro on 2 nm in all three iPhones&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;All three&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 18 Pro gets at least $100 pricier&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;$1,199 / $1,299 — exactly +$100&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable aperture on the Pro main camera&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;Yes, plus manual aperture and shutter&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ceramic Apple Watch Series 12&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;Ceramic finish in the lineup&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AirPods 5 with a new H3 chip&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;H3, $129 / $149 — ANC on both models&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home hub is shown (bet against)&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;td&gt;Not shown&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Something not in any leak (bet against)&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;Front camera under the foldable's inner display&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Brier score: &lt;strong&gt;0.118&lt;/strong&gt; (answering 50/50 on everything scores 0.25). I missed exactly where I warned I would: specs leak for months because thousands of factory workers know them, while the name is known by about twenty people who stay quiet. And I got cocky betting against a surprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was announced
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;US price&lt;/th&gt;
&lt;th&gt;Pre-order&lt;/th&gt;
&lt;th&gt;Available&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 18 Pro&lt;/td&gt;
&lt;td&gt;from $1,199&lt;/td&gt;
&lt;td&gt;Sep 12&lt;/td&gt;
&lt;td&gt;Sep 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 18 Pro Max&lt;/td&gt;
&lt;td&gt;from $1,299&lt;/td&gt;
&lt;td&gt;Sep 12&lt;/td&gt;
&lt;td&gt;Sep 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone Duo&lt;/td&gt;
&lt;td&gt;from $1,999&lt;/td&gt;
&lt;td&gt;Oct 16&lt;/td&gt;
&lt;td&gt;Oct 23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple Watch Series 12&lt;/td&gt;
&lt;td&gt;from $399&lt;/td&gt;
&lt;td&gt;now&lt;/td&gt;
&lt;td&gt;Sep 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple Watch Ultra 4&lt;/td&gt;
&lt;td&gt;from $799&lt;/td&gt;
&lt;td&gt;now&lt;/td&gt;
&lt;td&gt;Sep 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AirPods 5&lt;/td&gt;
&lt;td&gt;$129 / $149&lt;/td&gt;
&lt;td&gt;now&lt;/td&gt;
&lt;td&gt;Sep 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS 27, macOS Golden Gate &amp;amp; co&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Sep 14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The good:&lt;/strong&gt; a foldable whose two displays share (almost) the same proportions, so content scales instead of reflowing; Split View on an iPhone for the first time; a real variable aperture; 45 hours of video on the Pro Max; ANC in the $129 AirPods; and Apple Reference Image, which signs sensor data so a photo can later be proven to be a photo — arguably the most underrated announcement of the night.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The not-so-good:&lt;/strong&gt; +$100 on the Pro and +$300 on the 1 TB tier, price bumps on older models; Siri AI ships in beta, English-only, with daily usage limits and "expanded access for a fee in the future" (and not at all on iPhone in the EU or mainland China); the iPhone 18 Pro display is unchanged apart from a narrower Dynamic Island; the Duo is eSIM-only and doesn't support Apple Pencil Pro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not shown:&lt;/strong&gt; the base iPhone 18, the home hub, HomePod mini, Apple TV, any Mac — and, for developers, the iOS 27.1 SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new screen in numbers
&lt;/h2&gt;

&lt;p&gt;For 19 years the iPhone has been a tall, narrow strip: 1.5 on the original, 2.17 on today's Pro Max. Every mobile layout we write grew up inside that strip. The Duo's inner display breaks it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;iPhone 18 Pro Max&lt;/th&gt;
&lt;th&gt;Duo, outer&lt;/th&gt;
&lt;th&gt;Duo, inner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Diagonal&lt;/td&gt;
&lt;td&gt;6.9″&lt;/td&gt;
&lt;td&gt;5.4″&lt;/td&gt;
&lt;td&gt;7.6″&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pixels&lt;/td&gt;
&lt;td&gt;1320 × 2868&lt;/td&gt;
&lt;td&gt;1398 × 2034&lt;/td&gt;
&lt;td&gt;2670 × 1878&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Points (w × h)&lt;/td&gt;
&lt;td&gt;440 × 956&lt;/td&gt;
&lt;td&gt;466 × 678&lt;/td&gt;
&lt;td&gt;890 × 626&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Density&lt;/td&gt;
&lt;td&gt;460 ppi&lt;/td&gt;
&lt;td&gt;460 ppi&lt;/td&gt;
&lt;td&gt;430 ppi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aspect ratio&lt;/td&gt;
&lt;td&gt;2.17&lt;/td&gt;
&lt;td&gt;1.45&lt;/td&gt;
&lt;td&gt;1.42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size classes (portrait)&lt;/td&gt;
&lt;td&gt;compact × regular&lt;/td&gt;
&lt;td&gt;compact × regular&lt;/td&gt;
&lt;td&gt;regular × regular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supported orientations&lt;/td&gt;
&lt;td&gt;honored&lt;/td&gt;
&lt;td&gt;honored&lt;/td&gt;
&lt;td&gt;ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;1.42 is roughly √2 — the A4 paper ratio.&lt;/strong&gt; Fold such a sheet in half and each half keeps the same shape. In Split View the inner display splits into two panes of roughly 445 × 626 points, each almost the shape of the outer display. Two apps side by side each get an "outer screen". That's hardware designed with software in mind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The open Duo is wider than tall.&lt;/strong&gt; It's not a phone turned sideways — it's the new default posture. The Dynamic Island sits vertically on the side, so safe-area insets are asymmetric, and with the 27.1 SDK vertical bars move to the left edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16:9 video loses.&lt;/strong&gt; At 890 points wide a video is 890 × 501, leaving 125 points of black — a fifth of the screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How the Duo shows your app if you do nothing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Built without the iOS 27 SDK:&lt;/strong&gt; a familiar phone-sized window in the middle of the inner display, surrounded by black. A postcard in a giant frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built with the iOS 27 SDK:&lt;/strong&gt; wider, but part of the screen is still empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built with the iOS 27.1 SDK:&lt;/strong&gt; edge to edge, toolbars and tab bars move to the left. This is the native look — and the SDK for it isn't even in beta yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to grep for this week
&lt;/h2&gt;

&lt;p&gt;The good news: almost all of this is stuff Apple has asked us to do for years. The bad news: iPhones were always narrow, and plenty of codebases quietly relied on it. The Duo is still an iPhone, but inside it's regular × regular, like an iPad.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In your code&lt;/th&gt;
&lt;th&gt;Why it breaks on Duo&lt;/th&gt;
&lt;th&gt;Replace with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UIScreen.main.bounds&lt;/code&gt;, hardcoded widths&lt;/td&gt;
&lt;td&gt;Two differently shaped screens, the app moves between them live&lt;/td&gt;
&lt;td&gt;Your view's geometry (&lt;code&gt;onGeometryChange&lt;/code&gt;), screen via &lt;code&gt;UIWindowScene&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface orientation checks&lt;/td&gt;
&lt;td&gt;The inner display ignores supported orientations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;horizontalSizeClass&lt;/code&gt; / &lt;code&gt;verticalSizeClass&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;userInterfaceIdiom == .phone&lt;/code&gt; as "narrow screen"&lt;/td&gt;
&lt;td&gt;Duo is an iPhone with regular × regular and Split View&lt;/td&gt;
&lt;td&gt;Layout by size class, not by device type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symmetric safe-area insets&lt;/td&gt;
&lt;td&gt;The Dynamic Island is on the side&lt;/td&gt;
&lt;td&gt;Handle each inset and margin independently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UIRequiresFullScreen&lt;/code&gt; in Info.plist&lt;/td&gt;
&lt;td&gt;No longer prevents resizing from iOS 27&lt;/td&gt;
&lt;td&gt;Support live resizing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hardcoded "Sign in with Face ID" label&lt;/td&gt;
&lt;td&gt;Duo has Touch ID in the side button&lt;/td&gt;
&lt;td&gt;Branch on &lt;code&gt;LAContext.biometryType&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One window per app&lt;/td&gt;
&lt;td&gt;Split View opens multiple scenes, new windows only on the inner display&lt;/td&gt;
&lt;td&gt;Support multiple scenes and handle refused requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Biometrics is the easy one:&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;LocalAuthentication&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;biometryLabel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LAContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canEvaluatePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceOwnerAuthentication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;biometryType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;faceID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Sign in with Face ID"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;touchID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sign in with Touch ID"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;opticID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sign in with Optic ID"&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"Sign in with passcode"&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;Layout isn't rocket science either if you're on SwiftUI — &lt;code&gt;NavigationSplitView&lt;/code&gt; collapses to one column in compact width and expands on the inner display:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;RootView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontalSizeClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;hSize&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationSplitView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;SidebarList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hSize&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regular&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&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;Apple's "Preparing your app for iPhone Duo" material also names foldable-specific APIs: &lt;code&gt;onHingeChange&lt;/code&gt; / &lt;code&gt;UIHingeInteraction&lt;/code&gt; for hinge states and angle, &lt;code&gt;ArrangementView&lt;/code&gt; / &lt;code&gt;UIArrangementViewController&lt;/code&gt; for two-pane layouts around the fold, &lt;code&gt;reservedRegion&lt;/code&gt; on &lt;code&gt;GeometryProxy&lt;/code&gt; / &lt;code&gt;UIView&lt;/code&gt; to keep controls off the fold, &lt;code&gt;CameraCaptureAccessory&lt;/code&gt; for a preview or teleprompter on the outer display, and &lt;code&gt;AVCaptureDeviceDirectionCoordinator&lt;/code&gt; — because camera position is no longer camera direction.&lt;/p&gt;

&lt;p&gt;Two easy-to-miss extras: StandBy on the Duo runs on either display even when not charging, so apps without widgets or Live Activities simply don't exist there. And health apps should expect a lot more data — Series 12 and Ultra 4 sample heart rate every five seconds all day.&lt;/p&gt;

&lt;h3&gt;
  
  
  The next six weeks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sep 14&lt;/strong&gt; — iOS 27 ships. Its SDK (already in RC) decides whether your app resizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 16–17&lt;/strong&gt; — Apple group labs on iPhone Duo; Q&amp;amp;As on Sep 23.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xcode 27.1 beta&lt;/strong&gt; — iOS 27.1 SDK and a Duo simulator you can open, close, rotate and partially fold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oct 23&lt;/strong&gt; — iPhone Duo ships on iOS 27.1, and people who paid $1,999 start opening your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;If you own an iPhone 16 Pro or 17 Pro, you could have skipped this keynote. If you write iOS apps, this is the busiest autumn since 2017, when the iPhone X taught everyone the words "safe area". This time the lesson is that an iPhone can be wider than it is tall, that size classes on a phone aren't a formality, and that one window per app is no longer a given. The keynote was boring precisely because everything important in it was for Xcode, not for the store window.&lt;/p&gt;

&lt;p&gt;How many &lt;code&gt;UIScreen.main&lt;/code&gt; references are in your project?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://klukyanov.ru/notes/apple-event-september-2026-itogi/" rel="noopener noreferrer"&gt;klukyanov.ru&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swiftui</category>
      <category>apple</category>
      <category>mobile</category>
    </item>
    <item>
      <title>UIKit and SwiftUI Interoperability Patterns</title>
      <dc:creator>rahul bandal</dc:creator>
      <pubDate>Sun, 13 Sep 2026 13:37:01 +0000</pubDate>
      <link>https://dev.to/rahul_bandal_afe623cbf2e2/uikit-and-swiftui-interoperability-patterns-oal</link>
      <guid>https://dev.to/rahul_bandal_afe623cbf2e2/uikit-and-swiftui-interoperability-patterns-oal</guid>
      <description>&lt;p&gt;As iOS developers, we often find ourselves working on projects that started in the UIKit era but are now embracing SwiftUI for new features and UI components. Or perhaps you're building a new app in SwiftUI but need to leverage a complex, existing UIKit component or a third-party library that doesn't yet have a SwiftUI equivalent. In these scenarios, knowing how to seamlessly integrate UIKit and SwiftUI is not just a "nice-to-have" skill – it's essential.&lt;/p&gt;

&lt;p&gt;This article will guide you through the core patterns for interoperability,…&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Read the full article on Swift By Rahul&lt;/strong&gt; → &lt;a href="https://www.swiftbyrahul.com/posts/UikitAndSwiftuiInteroperabilityPatterns/" rel="noopener noreferrer"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete tutorial includes Swift code examples, SVG diagrams, and step-by-step explanations on our website.&lt;/p&gt;

</description>
      <category>uikit</category>
      <category>swiftui</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
