<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Anton’s Substack]]></title><description><![CDATA[My personal Substack (and that's true!) about iOS Development]]></description><link>https://antongubarenko.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!mzA2!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eb672de-f6cd-47a2-98b2-6de3f5be6ffe_2316x3088.jpeg</url><title>Anton’s Substack</title><link>https://antongubarenko.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 13 Jun 2026 09:09:23 GMT</lastBuildDate><atom:link href="https://antongubarenko.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Anton Gubarenko]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[antongubarenko@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[antongubarenko@substack.com]]></itunes:email><itunes:name><![CDATA[Anton Gubarenko]]></itunes:name></itunes:owner><itunes:author><![CDATA[Anton Gubarenko]]></itunes:author><googleplay:owner><![CDATA[antongubarenko@substack.com]]></googleplay:owner><googleplay:email><![CDATA[antongubarenko@substack.com]]></googleplay:email><googleplay:author><![CDATA[Anton Gubarenko]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[
WWDC26: Camera and Photo Technologies Group Lab - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-camera-and-photo-technologies</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-camera-and-photo-technologies</guid><pubDate>Fri, 12 Jun 2026 15:00:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/02a87607-cc07-443d-b557-215650457bd2_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Camera and Photos APIs are full of tradeoffs: speed versus quality, preview versus capture, metadata versus UI, and convenience APIs versus low-level control. This lab focused on practical questions around PhotoKit, AVFoundation, Core Image, thumbnails, depth, deferred start, and capture behavior.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>When Reframe or Clean Up is used to modify a photo, does iOS 27 tag it with metadata or content credentials so someone can tell the image was edited by AI?</h2><p>Yes. The file metadata is modified.</p><p>The panel said that IPTC metadata is updated, together with EXIF, and the IPTC metadata reflects which AI modification was used, such as spatial reframe or Clean Up.</p><p>In the Photos app, this information is also visible in the info panel. On iOS, when you swipe up on a photo, the info panel can show which edit was used.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=457s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Keywords were mentioned as a feature coming to Photos. Will there be an API for third-party apps to view or edit keywords?</h2><p>Keywords are available in Photos on iOS, matching a feature that has existed for a long time on macOS.</p><p>In the Photos app, users can see and edit keywords in the info panel. There is also UI for managing keywords and searching by them.</p><p>When exporting, keywords are reflected in IPTC metadata.</p><p>However, there is currently no PhotoKit-level API for fetching, editing, or querying Photos library assets by those keywords.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=531s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When showing thumbnails of images in a lazy grid view that also use matched geometry effect to a full image detail view, what is the optimal way to load thumbnails for performance?</h2><p>Do not decode full-size images when all you need is a thumbnail.</p><p>On the Core Graphics side, there are image-opening options that let Core Graphics use an existing embedded thumbnail if one is available, or decode and scale down the main image as efficiently as possible.</p><p>On the Core Image side, you can request a scale factor when opening images. That lets Core Image scale the image down early in the processing pipeline, so later work operates on the smaller image and runs faster.</p><p>The practical goal is to make thumbnail generation match the UI&#8217;s real size needs. Store or request smaller representations where possible, and avoid doing expensive full-resolution work for grid cells.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=594s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can deferred start cause issues where the user may attempt to capture a photo before the capture photo output has attached to the session?</h2><p>Yes, if you only defer initialization, you can still miss the shot.</p><p>Deferred start pushes initialization work later so the camera can launch quickly. But if the user taps capture before the photo output is fully ready, that can create a race unless you use the right capture behavior.</p><p>The panel pointed to <code>isResponsiveCaptureEnabled</code> on <code>AVCapturePhotoOutput</code>. When enabled together with deferred start, the system adds buffering so the app can launch quickly and still queue the capture even if the output is not fully initialized yet.</p><p>Deferred start was introduced in iOS 26. The common pattern is to prioritize the preview first and defer less critical outputs so the camera feels responsive immediately.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=691s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the best way to get the depth map and the image for a live viewfinder and for the taken image to create a nice 3D effect with both?</h2><p>There are two parts: preview and still capture.</p><p>For a live preview where you want direct access to depth samples, use <code>AVCaptureDepthDataOutput</code> together with the video stream, and use <code>AVCaptureDataOutputSynchronizer</code> so the RGB and depth frames arrive with matching timestamps.</p><p>For still capture, enable depth delivery on <code>AVCapturePhotoOutput</code>. The specific setting mentioned was <code>depthDataDeliveryEnabled</code>on <code>AVCapturePhotoSettings</code>, so the still photo capture includes depth data.</p><p>If you only need a live depth-style preview effect similar to the Camera app&#8217;s cinematic behavior, there is a higher-level shortcut: enable the cinematic video capture API. That can give you the depth effect in the preview without manually handling all depth samples yourself.</p><p>For custom effects, once you have the video/depth or still/depth pair, you can combine them with Core Image filters.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=782s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When <code>photoQualityPrioritization</code> is set to <code>.quality</code>, <code>AVCaptureDevice</code> often overrides manual exposure duration and ISO during capture. Is there a way to know the final exposure settings before the photo is captured?</h2><p>Manual exposure settings are only fully respected for speed-prioritized captures.</p><p>For photo mode, both balanced and quality capture can use multi-image processing and fusion. That may include underexposed and normally exposed frames, and the pipeline chooses capture settings to support that processing.</p><p>So if balanced or quality appears to respect your manual settings, that may only be because the current scene and pipeline happened to choose something close. It is not a guarantee.</p><p>The practical answer is: if you need strict manual exposure control, use speed prioritization. If you use balanced or quality, expect the system to optimize for capture quality and processing needs rather than preserving your manual exposure and ISO settings exactly.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=942s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can you elaborate on <code>PHAsset</code> original resource choice and what it is used for?</h2><p><code>PHAsset</code> original resource choice is related to assets that contain more than one original-like resource, such as RAW plus JPEG or RAW plus HEIF.</p><p>In Photos, users can choose whether edits are based on the RAW image or the compressed image. That choice also affects which resource Photos uses to generate smaller derivatives and thumbnails.</p><p>The new API exposes that choice to developers. You can inspect or change which resource is treated as the original, and therefore which source is used for editing and derivative generation.</p><p>The panel mentioned APIs around content editing input source, and change requests on assets for toggling which resource is considered original.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=1110s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Will iOS 27 support a linear scene-referred preview stream for Bayer RAW capture through <code>AVCaptureVideoDataOutput</code> or <code>AVCaptureVideoPreviewLayer</code>, without tone mapping or computational processing?</h2><p>Today, the way to get scene-referred linear-style data from camera capture is through the log formats, including Log and Log 2.</p><p>The panel explained that if the request is really for RAW data coming out of camera capture and then using the same filters used for still images, that is not currently available.</p><p>For ProRAW capture, the RAW frames include metadata, but that metadata is not compatible with what <code>CIRAWFilter</code> would need to render the image as if it were a still DNG workflow.</p><p>The team said the idea is worth exploring and thanked the developer for filing Feedback Assistant. For now, this is not supported as an out-of-the-box preview stream.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=1200s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For an app that lives and dies by its capture experience, what are the must-have recommendations?</h2><p>Performance and reliability come first.</p><p>A capture app exists to catch moments. If the app launches slowly or misses the shot, the user may not get that moment back. The panel&#8217;s first recommendations were fast launch, responsive capture, and deferred processing.</p><p>But the right capture experience depends on the product. A pro photography app, a social video app, and a playful filtered camera may all need different priorities. The team encouraged developers to understand who the app is for and what differentiates it.</p><p>Use Apple&#8217;s sample code rather than starting from scratch. AVFoundation is large and easy to misuse. A common mistake is driving <code>AVCaptureSession</code> from the main thread, which can block UI during long operations. The sample code shows best practices such as using a dedicated serial queue.</p><p>Also think about the Photos side of the experience. If your app captures photos, users expect those photos to go somewhere. Integrate with PhotoKit permissions thoughtfully: you can request save-only access first, then ask for broader read access only if the app needs it.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=1335s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the optimal recommended way for streaming video and audio simultaneously without sync drift?</h2><p>Use <code>AVCaptureSession</code> for both audio and video when possible.</p><p>Attach a camera input and a microphone input to the same session, then use audio and video outputs from that session. AVFoundation will do the hard work of putting the audio and video samples on a coherent timeline.</p><p>If you use a different audio API, such as AU RemoteIO, you need to synchronize clocks yourself. Each device is backed by a time source, and you may need to convert presentation timestamps between clocks using Core Media timing APIs.</p><p>The usual approach is to preserve the audio timeline and align video timestamps to it, because audio glitches and timing shifts are easier for users to notice than tiny video timing adjustments.</p><p>If you stream over a network, keep the timestamps coherent before sending. On playback, use AVFoundation playback APIs such as <code>AVPlayer</code> or <code>AVSampleBufferDisplayLayer</code> to handle synchronization on the receiving side.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=1676s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Why does ProRAW support 48 MP output from a Quad Bayer sensor, while native Bayer RAW output is limited to binned resolution?</h2><p>ProRAW and Bayer RAW are different kinds of output.</p><p>Bayer RAW contains Bayer sensor data. ProRAW goes through debayering and Apple&#8217;s Photonic Engine-style merging before output. By that point, the data is already linearized RGB, so ProRAW no longer has to care whether the original sensor pattern was Bayer or Quad Bayer.</p><p>Quad Bayer RAW output would not simply be ordinary Bayer RAW at full resolution. It would be Quad Bayer data, and the ecosystem would also need a way to decode it, including support in tools such as <code>CIRAWFilter</code>.</p><p>The panel said this is heavier than it sounds, and encouraged developers who need Quad Bayer RAW output to file feedback.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=1911s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the recommended process to generate and write ISO-conformant gain maps and gain map metadata for HEIF and JPEG on iOS?</h2><p>Apple already covered this in a previous WWDC session focused on HDR and gain maps.</p><p>The panel pointed developers to that session for API details. The high-level answer is that Core Graphics and Core Image both support gain map workflows.</p><p>Core Image gives deep control over the output: the look, the headroom, and how SDR/RGB content is combined with the HDR addition. Core Graphics also supports writing the relevant metadata.</p><p>The ISO gain map specifications have been added to both HEIF and JPEG, and Apple has worked on that standards support. Developers who want to understand the metadata deeply can also look at the underlying specifications.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2089s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How should <code>setPreparedPhotoSettingsArray</code> work together with deferred start?</h2><p>They are complementary, not conflicting.</p><p>Deferred start is about launching quickly: it lets the session get preview running first while deferring initialization for less immediate branches like photo or movie outputs.</p><p><code>setPreparedPhotoSettingsArray</code> is about telling the photo output what the worst-case still capture configuration might be, so the pipeline can pre-allocate resources for that case.</p><p>You can use both. Deferred start moves initialization after preview starts, while prepared settings let the photo output prepare for expensive capture settings. You can also prepare or re-prepare settings later.</p><p>Without deferred start, photo output quality can affect launch time because quality capture may require heavier allocations. With deferred start, preview can start quickly and those allocations can happen later.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2265s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Will adjusting <code>PHAsset</code>&#8217;s new rating property require a Photo Library change request?</h2><p>Yes.</p><p>The panel said this is handled through <code>PHAssetChangeRequest</code>. The new rating property can be changed per asset, and the rating enum includes unset plus one-through-five values.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2395s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there a native way to obtain the original file type, such as PNG or JPEG, from a photo selected with <code>PhotosPicker</code>?</h2><p>Check how you configure <code>Transferable</code>.</p><p>The panel suggested making sure you set the <code>UTType</code> explicitly rather than relying on a default image type. If you leave it too generic, you may see output that does not match what you expected.</p><p>There can also be conversion in the picker in some situations. For example, if the user disables sharing location or captions with your app, Photos may convert from some formats such as RAW.</p><p>If everything is coming out as PNG, double-check the <code>UTType</code> used for transfer and review Apple&#8217;s PhotosPicker sample code.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2430s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there a way to learn about the adjustment plist format that Apple Photos writes, and can those adjustments be imported back into Photos?</h2><p>No public API exists for this today.</p><p>The panel said there is no supported way to decode that adjustment plist or import those Photos app adjustments back into Photos through a public API.</p><p>If this is important for your app, file a Feedback Assistant request.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2510s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the biggest mistakes developers make when building camera-heavy apps on iPhone?</h2><p>The first big mistake is using <code>AVCaptureSession</code> on the main thread.</p><p><code>AVCaptureSession</code> can block while it reconfigures the capture graph. If you start or reconfigure it on the main thread, your UI can hang. Use a dedicated serial queue for capture session work.</p><p>The second mistake is changing multiple session properties without <code>beginConfiguration</code> and <code>commitConfiguration</code>. If you make disruptive changes one by one, the session may re-evaluate and reconfigure the graph after each change. Wrap related changes in one transaction so the graph is updated once.</p><p>Another common mistake is using <code>AVCaptureVideoDataOutput</code> just to render preview. If you do not need to inspect or modify frames, use <code>AVCaptureVideoPreviewLayer</code>. It is highly optimized. Use video data output only when you need direct access to buffers for processing, overlays, histograms, metadata, or similar work.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2564s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there any API to access the new Spatial Reframe pipeline used by Photos?</h2><p>No. There is no public developer API for the Photos app&#8217;s Spatial Reframe capability.</p><p>Some parts of ARKit can capture and reconstruct 3D scenes, but that is not the same as a ready-made Photos Spatial Reframe editing pipeline.</p><p>If you are interested in live capture with depth, AVFoundation can use LiDAR depth on supported Pro iPhones, or TrueDepth on the front-facing camera. But that is separate from the Photos app&#8217;s Spatial Reframe feature.</p><p>The panel recommended filing feedback if developers want access to this kind of pipeline.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=2979s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When will camera capture rotation/orientation handling be made easier across iOS, iPadOS, and macOS?</h2><p>The panel acknowledged that orientation handling can be tricky across Apple platforms, because different devices and UI contexts treat orientation differently.</p><p>The recommendation was to use the newer rotation coordination APIs where available, rather than manually building one-off orientation logic for every platform.</p><p>They also encouraged developers to file feedback for cases that are still painful, especially when platform differences make capture output hard to reason about.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=3053s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can third-party apps capture 24 MP photos with depth from both cameras, similar to the system Camera app?</h2><p>The panel&#8217;s answer was cautious: capabilities depend on device, camera, format, and selected capture configuration.</p><p>Depth delivery and high-resolution photo capture are each supported through AVFoundation APIs, but not every combination is available on every device or every camera. Some system Camera app behaviors depend on private pipeline choices or supported formats that third-party apps must query at runtime.</p><p>The practical guidance is to inspect the available <code>AVCaptureDevice</code> formats, supported photo dimensions, and depth delivery support for the exact device and camera you are using. Do not assume the system Camera app&#8217;s full behavior is exposed as a single third-party API combination.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=3265s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How should developers think about multicam capture and streaming from multiple iPhone cameras?</h2><p>Use the supported multicam APIs and design around device limits.</p><p>Multiple simultaneous camera streams can be expensive in terms of processing, memory bandwidth, thermal load, and power. The available combinations depend on hardware and formats.</p><p>For streaming, keep audio/video synchronization in mind, use coherent timestamps, and avoid doing unnecessary per-frame processing on the critical path. If you are only showing preview, prefer preview layers. If you need buffers for encoding or effects, keep processing efficient and expect to manage back pressure.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=3500s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What should developers know about the new camera control APIs and niche capture features?</h2><p>The later part of the lab covered several specialized APIs and edge cases.</p><p>The overall pattern was consistent: query capabilities at runtime, use the most specialized API only when the app really needs it, and file feedback when the system Camera app exposes behavior that third-party APIs cannot yet reproduce.</p><p>For pro camera apps, the team emphasized careful format selection, performance testing, and clear user experience decisions. A feature may be technically possible, but it still needs to fit the app&#8217;s purpose and the user&#8217;s expectations.</p><p><a href="https://www.youtube.com/watch?v=fn4F09dQb3s&amp;t=3530s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical Camera and Photos questions throughout the session. Your questions made the discussion useful for developers working on AI-edited image metadata, Photos keywords, thumbnails, fast camera launch, depth maps, still capture, deferred start, responsive capture, photo quality prioritization, PhotoKit resource choice, RAW workflows, gain maps, audio/video synchronization, PhotosPicker, rotation, Spatial Reframe, and camera-heavy app performance.</p><p>Question acknowledgments: Florent INF, Ben, SJK_27, Yelon, Eric, and the online WWDC audience who submitted and upvoted the remaining Camera and Photo Technologies questions.</p><p>Finally, a heartfelt thank-you to Sergey, Matt Deoff, Brad Ford, Ivan Cavo, David Konchon, Jake, and the teams behind the scenes for leading the session and explaining how to build better camera and photo experiences.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: Icon Composer for Beginners Group  - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-icon-composer-for-beginners</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-icon-composer-for-beginners</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Fri, 12 Jun 2026 12:03:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/959aaef0-f34e-44b8-a96b-544cb110a3e9_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Icon Composer is the final authoring step for app icons that use the Liquid Glass rendering system. The main idea from the lab: design the icon clearly first, then use Icon Composer to annotate layers, tune glass behavior, and verify the icon across appearances, sizes, and backgrounds.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>If you&#8217;re designing an icon from scratch today, what would your step-by-step workflow in Icon Composer look like?</h2><p>Start before Icon Composer. First, decide on the visual metaphor for the app: what the icon should communicate, whether it references the app&#8217;s purpose directly, or whether it borrows from a distinctive part of the app&#8217;s interface.</p><p>Then draw the icon in a design tool. The panel recommended thinking carefully about composition, layer organization, and how the artwork will scale. Simple, elegant vector shapes are usually a better foundation than very complex detail, because icons need to work from very small system UI sizes all the way up to large marketing uses.</p><p>After that, export the layers from the design tool as individual files and bring them into Icon Composer. If you export layers at the full canvas size, their relative positions are preserved when imported, which saves time and avoids surprises.</p><p>Icon Composer then becomes the second step: annotate the layers, tune color behavior, choose which layers use Liquid Glass, adjust specular/refraction/shadow behavior, and check the icon in light, dark, clear, mono, and tinted modes.</p><p>Apple provides templates for common design tools, including Figma, Sketch, Illustrator, and Photoshop. The Illustrator template includes a script that helps preserve positioning relative to the canvas.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=722s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When using Icon Composer, what are the most common mistakes developers make that result in icons looking great in Icon Composer but weak on device?</h2><p>The big mistake is not testing the icon in the contexts where it will actually appear.</p><p>An icon can look beautiful on the Icon Composer canvas and still fail at small sizes, in dark mode, in clear mode, in tint mode, or over a busy background. The panel emphasized checking the icon at its smallest and largest common presentation sizes, and using the preview tools rather than judging only the default canvas view.</p><p>Complexity is another common problem. Liquid Glass effects can be subtle and powerful, but they work best when the underlying shape language is clear. Too much fine detail can make the icon weaker once rendered on device.</p><p>Use Icon Composer&#8217;s waterfall-style size preview, appearance previews, background previews, and grid overlays to verify the result. The goal is not only to make the icon attractive, but to make it legible and recognizable across the whole system.</p><p>The old advice still applies: put the icon on a Home Screen, inside a folder, and check it on a real device in daylight. If it does not hold up there, keep simplifying or tuning.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=951s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Many developers already have mature workflows in Figma, Sketch, or 3D tools. What problem was Apple trying to solve with Icon Composer?</h2><p>Icon Composer was created because app icons are not just static images anymore.</p><p>An icon is part brand identity, part product identity, part system UI, and part user-facing object that appears across many Apple devices. It needs to work on iPhone, iPad, Mac, Apple Watch, dark mode, clear mode, tint mode, different backgrounds, different sizes, and future rendering conditions.</p><p>A static bitmap cannot adapt well to all of that. You could ship many separate PNGs, but that creates a large, brittle set of assets that does not evolve with the system.</p><p>Icon Composer gives developers a resolution-independent representation. You provide clear artwork and layer structure, and the system can render it with high quality across device contexts and design modes.</p><p>It also brings the material language of the system into app icons. Liquid Glass can be applied in a way that interacts with individual layers, rather than being a superficial effect placed over the whole icon.</p><p>The broader vision is consistency and adaptability: one source file that can produce a polished icon across many environments while still letting designers tune the result.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=1129s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What concrete recommendations should developers follow to upgrade icons built for the iOS 26 Liquid Glass design system to iOS 27?</h2><p>If you already have an Icon Composer file from last year, open it in the updated Icon Composer first. The new rendering is applied automatically, so you can see how your existing icon looks without changing artwork or annotations.</p><p>From there, review specular highlights, especially if you have overlapping layers. Check what automatic does, then decide whether inside or outside specular works better for the colors and layer structure.</p><p>Review shadows and refraction as well. iOS 27&#8217;s rendering can produce a sharper look, and refraction can be tuned more precisely.</p><p>One major design change the team made in Apple&#8217;s own icons was reducing translucency. This does not happen automatically, so developers should review their own icons and decide whether translucency should be reduced to make the icon sharper and more legible.</p><p>If you still use a legacy bitmap icon, iOS 27 will do its best to segment and adapt it, but you may see choices you do not like. If you want full control, now is the time to invest in an Icon Composer version of the icon.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=1546s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can you explain blend modes in Icon Composer, and when should developers use them?</h2><p>Blend modes define how one layer&#8217;s pixels interact with the pixels beneath them.</p><p>The simplest mode is normal blending: the foreground layer replaces what is underneath. Other blend modes let brightness, hue, color, vibrancy, or darker/lighter relationships from the underlying layers contribute to the final result.</p><p>In Icon Composer, blend modes are useful because glass is not just a flat opaque object. Real glass lets color, brightness, and depth interact. Blend modes help layers feel like transparent or refractive material instead of simple stacked artwork.</p><p>For example, <code>plusL</code> can retain more vibrancy than a standard screen-like blend, which can help an icon feel like colorful glass. <code>plusDarker</code> can help darker relationships preserve color intensity. Multiply, screen, lighten, darken, and the other modes each create different foreground/background relationships.</p><p>There is no single correct blend mode. The panel&#8217;s advice was to try them in context and choose the one that makes the icon&#8217;s layers communicate the right material effect while preserving legibility.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=1845s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>iOS 27 lets users adjust Liquid Glass transparency systemwide. Does Icon Composer let us preview across that slider range, and what should we do if a clear mode icon fails contrast at an extreme?</h2><p>For app icons, clear mode glass does not respond to the systemwide Liquid Glass transparency slider.</p><p>That was a deliberate choice. System glass often knows more about foreground and background content, but app icons can contain almost anything. To keep icons consistent and legible, the glass behavior for clear mode icons is pinned instead of varying with the slider.</p><p>That means developers do not need to design across that slider range for icons. You still need to test against wallpapers, clear mode, dark clear, light clear, and tint modes, but not against every possible system glass transparency value.</p><p>There is one mono mode annotation that supports light clear, dark clear, and tinted modes, so focus on making that mono representation strong.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2198s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Most app icons start as flat 2D brand marks. How should we think about giving something flat a convincing physical appearance in Liquid Glass?</h2><p>This is exactly the kind of transformation Icon Composer is built for.</p><p>When you bring in a layer, Icon Composer applies a default set of glass properties, and then you can tune them based on the shape, size, color, and structure of the artwork.</p><p>The panel compared this to how brands already adapt logos across media. A logo may appear on paper, on a product, in a motion graphic, or as a physical badge. It is still the same identity, but each medium may require a different treatment.</p><p>For app icons, that may mean exposing layers that were flattened or cut out in the original vector artwork. If a mark has eyes, counters, or cutouts, you may decide those should become separate layers instead of negative space. That can make the icon feel constructed from real materials rather than printed flat.</p><p>The key is to preserve recognizability while adapting the construction for the medium. If the icon still clearly reads as the same brand, adding physicality through layers, glass, refraction, and shadows can make it feel at home in the system.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2303s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Has anything changed in how updated Icon Composer icons are included in an app in Xcode?</h2><p>No. The integration process is the same as before.</p><p>When you finish designing the icon in Icon Composer, drag the <code>.icon</code> file into your Xcode project as a normal resource, next to your Swift files.</p><p>Xcode should suggest the correct target membership by default, but you can verify it during import.</p><p>Then open the target editor and make sure the app icon name matches the file name you dragged in. Usually that means <code>AppIcon</code>, without the <code>.icon</code> extension.</p><p>This is adjacent to the asset catalog, not inside it. If you already have an app icon in the asset catalog, you likely want to remove it to avoid confusion.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2517s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Do imported layers inside Icon Composer automatically become glassy in the iOS 27 style?</h2><p>Yes. Imported layers are rendered as glass layers by default.</p><p>However, you can control this. If a layer should remain flat, or if you already prerendered a specific treatment into a raster image, you can turn off Liquid Glass for that layer.</p><p>If your icon has multiple layers, consider organizing them into groups. Glass properties are applied at the group level, so grouping gives you more control over how different pieces of the icon render.</p><p>This is useful for icons that combine vector layers, raster artwork, watermarks, or other elements where not everything should receive the same glass treatment.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2586s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>In tint mode, my icon background colors look washed out and lack contrast, while clear mode looks better. What settings can I tweak for better vibrancy?</h2><p>First, test in the latest iOS 27 Icon Composer rendering, because the team made tweaks to improve some of these cases.</p><p>Then check the mono mode annotations. The panel recommended maximizing dynamic range: use real darks and bright highlights so the system has enough contrast to work with when transforming the icon into tinted modes.</p><p>Pay attention to your background layer as well. If you supply your own gradient or background, be careful with luminance values, especially in mono mode. In many cases, using the system-provided backgrounds gives you better baseline contrast.</p><p>Foreground elements should usually include white or near-white areas, or at least some strong highlight component, so they remain legible across user-selected tint colors. Some tint colors, such as yellow, are naturally high in luminance, so your icon needs to survive those extremes.</p><p>Finally, inspect the shape detail. Very fine elements or too many small shapes can become weak in tint mode even if the colors are technically correct.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2668s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For the Apple Developer app icon, what are the main layers? Is it just a border, a background, and the hammer logo?</h2><p>Yes, the Apple Developer icon is relatively simple structurally.</p><p>The panel described it as essentially three major parts: the background, the hammer mark, and a border or rim treatment. The strength comes from the clarity of the metaphor and how those few pieces are tuned, not from a large number of layers.</p><p>This is a useful reminder that Liquid Glass does not require a complicated layer stack. A simple icon can work very well when the core shape is strong and the layers are organized cleanly.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=2960s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How should I design icons for color-blind users?</h2><p>Do not rely on color alone.</p><p>The panel recommended thinking in terms of contrast, shape, and clear visual structure. Color can help make an icon feel distinctive, but the icon should still be recognizable when color differences are reduced or transformed by system appearance modes.</p><p>For Icon Composer specifically, mono mode is important. Strong darks, bright highlights, and clear shapes help the icon survive tinted and monochrome transformations. If the icon only works because two colors are different, it may fail for color-blind users or in system modes that reduce or remap color.</p><p>The practical test is to preview the icon in mono, tinted, light, dark, and clear modes, and make sure the key shape still reads.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=3171s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I am a newbie with no design know-how. How can I make a good-looking app icon in Icon Composer?</h2><p>Start with the app&#8217;s purpose, not with the glass effects.</p><p>The panel&#8217;s advice was to find one clear metaphor that represents the app. A simple mark is easier to make recognizable, easier to test at small sizes, and easier for Icon Composer&#8217;s rendering to adapt across light, dark, clear, and tint modes.</p><p>Use Apple&#8217;s templates and sample files as a learning tool. Open them, inspect the layer structure, see how groups are organized, and try changing one variable at a time. That is a much better way to learn than starting with a very complex icon.</p><p>Do not try to make the first version perfect. Build a simple icon, preview it at the smallest size, check it on a real Home Screen, and ask whether the idea is still recognizable. Then tune the glass, refraction, shadows, and color.</p><p>If design is not your strength, keep the icon intentionally simple. A clear symbol with strong contrast will usually work better than a detailed illustration with lots of effects.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=3180s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are Apple&#8217;s recommendations for balancing one shared Liquid Glass icon with platform-specific tweaks while keeping the brand consistent across iOS, macOS, watchOS, and legacy OS versions?</h2><p>Use Icon Composer as the source of truth where possible.</p><p>The tool is designed to help one icon adapt across platform shapes and system appearances. For example, watchOS has a circular icon shape, while iOS and macOS use different rounded-square presentations. Icon Composer gives you a unified visual system while still letting the renderer adapt the icon to those different destinations.</p><p>For older OS versions, Apple has gone to significant lengths to render a reasonable version of the same icon identity. The goal is that the icon you land on in Icon Composer can still be represented naturally on legacy systems, App Store product pages, and earlier visual contexts, even when those systems do not support the newest Liquid Glass rendering.</p><p>That does not mean the icon will look identical everywhere. Older systems have different design languages and rendering capabilities. But the goal is to preserve the same recognizable brand identity across versions.</p><p>The practical advice is to design one strong, simple icon first, then use Icon Composer&#8217;s platform and back-deployment previews to check where platform-specific tweaks are needed. Preserve the core metaphor and silhouette; tune the rendering details around it.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=3443s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the team&#8217;s favorite new Icon Composer features this year?</h2><p>The panel called out several favorites.</p><p>The updated specular treatment was a big one. It gives glass layers sharper definition, including darker edge details that help describe the shape.</p><p>Refraction also stood out because it gives designers a more direct way to tune the physical feel of glass. You can control the height and strength of the refraction, and even use inverse refraction for different effects.</p><p>The updated shadows were another highlight. Ring shadows and chromatic or neutral shadow options help give icons more realistic material presence.</p><p>Finally, the improved previews matter a lot: checking different appearances, clear mode backgrounds, platform shapes, icon grids, sizes, and back-deployed rendering makes the tool more useful for real shipping work.</p><p><a href="https://www.youtube.com/watch?v=iaXx7XVK8c8&amp;t=3653s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical Icon Composer questions throughout the session. Your questions made the discussion useful for developers and designers working on app icon concepts, SVG layer preparation, Liquid Glass tuning, iOS 27 migration, blend modes, Xcode integration, tint mode, clear mode, dark mode, accessibility, cross-platform consistency, and device previews.</p><p>Question acknowledgments: Abashek 3075, Jodor Tree, Jason Chung, Jordan, Kyle, Thomas, SJK27, Andy Zoom01, Macverse, and the online WWDC audience who submitted and upvoted the remaining Icon Composer questions.</p><p>Finally, a heartfelt thank-you to Mike Stern, Patrick Heinen, John Hes, Lance, Liam, and the teams behind the scenes for leading the session and explaining how to move from icon idea to polished system-ready asset.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: SwiftUI Group Lab - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-swiftui-group-lab-q-and-a</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-swiftui-group-lab-q-and-a</guid><pubDate>Fri, 12 Jun 2026 09:01:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cdd0813f-ac4c-43c4-a3d4-94764c57cbec_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>SwiftUI rewards a different mental model than UIKit or AppKit. This lab focused less on single APIs and more on how SwiftUI wants you to think about architecture, view identity, invalidation, layout, built-in controls, debugging, and performance.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>Is there an architecture like MVC, MVVM, or another pattern that the SwiftUI team expects us to adopt?</h2><p>No. SwiftUI is designed to be architecture-agnostic.</p><p>Use the architecture that fits your app, your team, and your data model. A small app may not need a heavy architecture at all. A large app with many contributors may benefit from stronger conventions and more standardized patterns.</p><p>The important part is not to design the app around a UI framework alone. Your data model also has to support persistence, synchronization, testing, and other app-specific needs. Ideally, your SwiftUI views are a projection of that model into pixels.</p><p><code>@Observable</code> can help here because your model can remain plain Swift code. It can be tested, reused from UIKit or AppKit, and then observed by SwiftUI when you adopt SwiftUI incrementally.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=173s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the difference between an <code>@ViewBuilder</code> closure or computed property and a separate <code>View</code>? Are there performance pros and cons?</h2><p>A computed property or <code>@ViewBuilder</code> helper mostly behaves as if the code still lived inside the original view body. It helps readability, but SwiftUI does not get a new independent view identity from it.</p><p>A separate <code>View</code> is different. It has its own identity and its own <code>body</code>. SwiftUI can track dependencies and invalidation more granularly for that separate view.</p><p>That means extracting a subview can improve performance when only part of a larger view depends on a piece of state. The smaller view can update independently instead of causing the whole parent body to be reevaluated.</p><p>There is no hard lower bound for how small a view can be. Views are lightweight value types. Split when it clarifies the code, isolates dependencies, gives you finer previews, or lets you read environment values in the right scope.</p><p>The same idea applies to modifiers: extracting repeated modifier chains into a custom modifier can provide similar structure and dependency benefits.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=393s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are some common SwiftUI anti-patterns developers fall into?</h2><p>Several patterns came up.</p><p>First, watch for overuse of <code>GeometryReader</code>. If you are using it to drive layout, a custom <code>Layout</code> may be a better fit because it avoids invalidating a larger hierarchy unnecessarily.</p><p>Second, be careful with <code>onChange</code>. It is useful, but if you are using it to trampoline data back and forth, that may be a sign the view is still written in an imperative style. Prefer making the UI declaratively respond to state.</p><p>Third, avoid putting frequently changing values in the environment when many views read them. Environment is useful for passing data through the tree, but if the environment value changes often, every view that declares that dependency may be invalidated. Passing an observable object through the environment can be better when the object identity is stable and only specific properties change.</p><p>Fourth, avoid custom wrapper views around controls when a style protocol is the better tool. If you are wrapping <code>Button</code>only to apply a consistent look, a custom <code>ButtonStyle</code> often keeps you closer to SwiftUI&#8217;s built-in behavior and built-in initializers.</p><p>Finally, be careful with conditional modifiers that add or remove modifiers while the view is on screen. Changing the structure can recreate the view, break animations, reset state, and hurt performance. Prefer inert modifier values, such as changing opacity between <code>1</code> and <code>0</code>, instead of conditionally adding and removing the modifier itself.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=720s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is one SwiftUI concept that takes many developers the longest to understand?</h2><p>A major concept is how SwiftUI updates views.</p><p>SwiftUI does not work like a DOM diff where the framework simply compares rendered output. Internally, SwiftUI maintains a graph of views and dependencies. State changes flow through that graph, and SwiftUI uses those dependencies to decide what needs to be reevaluated.</p><p>This is why dependency scoping matters. If a large view reads a piece of state, the whole view may be reevaluated when that state changes. If a smaller extracted view reads it, SwiftUI can update that smaller unit instead.</p><p>Another common mental shift is that reevaluating a view body is not necessarily the same as redrawing or rerendering pixels. A body can be reevaluated without causing expensive rendering work if the resulting view description does not materially change.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=1155s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Why can <code>ForEach</code> with filtering or <code>if</code> statements become a problem?</h2><p>If you put filtering logic or <code>if</code> statements inside the body of a <code>ForEach</code>, the visual result may look correct, but SwiftUI may need to iterate through the whole <code>ForEach</code> to understand how many views it contains.</p><p>That can hurt performance, especially in large lists or lazy containers.</p><p>A better approach is to filter the data before it reaches the <code>ForEach</code>, so the <code>ForEach</code> receives a collection with a predictable number of elements. If type erasure or structural uncertainty is involved, wrapping content in a container such as <code>HStack</code> or <code>ZStack</code> can sometimes give SwiftUI a constant structural shape to reason about.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=1244s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How should developers think about SwiftUI layout compared with UIKit layout?</h2><p>UIKit and SwiftUI approach layout differently.</p><p>UIKit is often experienced as top-down: the outer container lays out child views and pushes constraints or frames downward.</p><p>SwiftUI often feels more bottom-up. Parents propose sizes, children decide what size they want, and those answers flow back upward. The final layout is the result of that negotiation.</p><p>This difference matters when mixing UIKit/AppKit and SwiftUI. If you build a &#8220;sandwich&#8221; or &#8220;cake&#8221; of interop layers, with frameworks alternating across the hierarchy, you need to be aware that each framework has different layout and invalidation expectations.</p><p>For deeper understanding, the panel pointed to earlier WWDC material on custom layout.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=1425s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What did the new <code>ContentBuilder</code> work change, and why does it matter?</h2><p><code>ContentBuilder</code> improves how SwiftUI handles complex view structures, especially views with many groups, sections, and <code>ForEach</code> blocks.</p><p>The panel used an analogy: older type-checking could feel like exploring every path through a cave. With the newer builder improvements, it becomes more like walking down a hallway.</p><p>The practical result is that SwiftUI code with nested structural containers can type-check more predictably. <code>ContentBuilder</code>also backports because it is effectively a type alias to <code>ViewBuilder</code>, while <code>ViewBuilder</code> itself became smarter.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=1766s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When your teams hit a mystery rerender storm, what is the real debugging workflow? Is it Instruments, <code>_printChanges()</code>, or something else?</h2><p>Use all of the tools.</p><p><code>_printChanges()</code> and <code>_logChanges()</code> are useful for seeing why a view body is being reevaluated. <code>_logChanges()</code> uses OS logging, which can be more convenient in some workflows.</p><p>Another simple visual trick is to temporarily apply a random color to a view. If the color changes unexpectedly, you can quickly see that the body is being reevaluated. This is also useful for comparing a computed <code>@ViewBuilder</code> helper with an extracted subview.</p><p>For deeper investigation, use the SwiftUI instrument in Instruments. It gives a more systematic view of invalidation and view update behavior.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=1879s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the performance tradeoffs of <code>compositingGroup</code> and <code>drawingGroup</code>?</h2><p><code>compositingGroup</code> is less about performance and more about visual correctness. It lets SwiftUI treat a group of views as a single composited result before applying effects.</p><p>For example, if you apply a shadow to a <code>ZStack</code>, you might expect one shadow around the combined visual result, but without compositing SwiftUI may apply the shadow to each individual visual element. <code>compositingGroup</code> lets you apply the effect to the overall result.</p><p><code>drawingGroup</code> is more performance-oriented. It can render many visual layers into a single drawing layer. This can help when you have many on-screen rendered elements, such as a complex custom graphic, and the cost is dominated by layer rendering rather than the number of SwiftUI value-type views.</p><p>That said, it is not a universal optimization. It trades memory and rendering behavior for fewer rendered layers, so profile before using it broadly.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=3481s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When should I use <code>Canvas</code>, and what are its tradeoffs?</h2><p><code>Canvas</code> is useful when you are drawing custom graphics where you do not need each individual piece to be its own SwiftUI view.</p><p>The tradeoff is that the things drawn inside a <code>Canvas</code> are not SwiftUI views. You cannot attach independent gestures to each drawn element the same way you would with separate views. If you need interaction, you may need to do your own hit testing or math.</p><p>Accessibility also needs extra care. A canvas can be visually rich but semantically empty unless you expose an accessible representation. The panel called out <code>accessibilityRepresentation</code> as a useful API: for example, a visual canvas slider could be represented to accessibility as three real sliders.</p><p><a href="https://www.youtube.com/watch?v=7g-Xg5xiH4o&amp;t=3651s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical SwiftUI questions throughout the session. Your questions made the discussion useful for developers thinking about architecture, view identity, invalidation, layout, performance, debugging, built-in controls, UIKit/AppKit interop, and accessibility.</p><p>Question acknowledgments: U.J., Azie, Tyler, and the online WWDC audience who submitted and upvoted the remaining SwiftUI and UI Frameworks questions.</p><p>Finally, a heartfelt thank-you to Kurt, Adicha, Jason, Taylor, David, Sema, and the teams behind the scenes for leading the session and explaining how to reason about SwiftUI in real apps.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: Coding Intelligence, Machine Learning & AI - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-coding-intelligence-machine</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-coding-intelligence-machine</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Fri, 12 Jun 2026 06:01:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f1ef1c45-96dc-42fb-b55b-a78e4e183df4_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apple&#8217;s AI stack is no longer one framework or one model choice. This lab covers how Foundation Models, Core AI, Core ML, MLX, Xcode agents, evaluations, Vision, Private Cloud Compute, local models, context management, guardrails, and model deployment fit together.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>Could you explain the roles of Core AI, Core ML, and MLX in simple terms from a beginner&#8217;s perspective? How should one understand them and decide which one to learn or use?</h2><p>Think of Apple&#8217;s machine learning stack as layers, depending on how much control you need.</p><p>If you are building an LLM-based feature, start with the Foundation Models framework. Try the system language model first, then use evaluations to check whether it works for your use case. If you need more capability or a larger context window, use Private Cloud Compute. If you need your own model, you can still plug it into Foundation Models through the language model protocol.</p><p>If your work is not language-model based &#8212; for example diffusion, image segmentation, or another custom neural network &#8212; use Core AI. Core AI is the forward-looking path for neural-network workloads and for bringing custom models into apps.</p><p>Core ML remains available, especially for more traditional ML use cases such as decision trees and similar model types.</p><p>MLX is the lower-level and flexible option, especially useful for training, experimentation, distributed inference, local AI workflows, and cases where you want more direct control over model execution.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=371s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the on-device Foundation Models context window in iOS 27? Is input and output counted against one shared token budget?</h2><p>The on-device system language model context window is 4,096 tokens. It is a shared budget, so input and output both count against the same window.</p><p>For example, if you send roughly 4,000 tokens as input, the model only has about 96 tokens left for output.</p><p>Private Cloud Compute supports a larger 32K context window, also as a shared budget. If you need even larger context windows, you can bring another provider or model through the language model protocol, including Core AI, MLX, or server-backed model packages.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=610s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can Foundation Models run inside a background app refresh task or background processing task, especially while the phone is locked, asleep, or the app has been backgrounded for a while?</h2><p>Yes, Foundation Models can run in the background, including inside background tasks. However, the system may rate-limit the app, especially if the OS is busy.</p><p>If that happens, the system language model can throw a rate-limited error. Your app should catch that error and try again later.</p><p>On macOS, the local Foundation Model should not be rate-limited as long as the app is in the foreground. Private Cloud Compute can also be rate-limited, but for different reasons, such as sending too many requests in a short period.</p><p>The quality of the output is not reduced by background execution. The request either runs or it does not; it may just take longer or require retrying if rate-limited.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=797s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For macOS 27 Apple Intelligence, what does the waitlist mean? Both Siri local and PCC models are working. Are we getting different models on the waitlist? Does this beta include AFM Core advanced 20B?</h2><p>The waitlist applies only to Siri. It does not apply to the Private Cloud Compute language model or to the on-device pieces used by the Foundation Models framework.</p><p>The panel also confirmed that the beta includes AFM Core advanced, used for voice features and related capabilities.</p><p>For deeper Siri-specific details, the recommendation was to bring the question to the Apple Intelligence group lab.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=902s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>The Foundation Models framework now supports bringing your own LLM provider alongside the on-device model and Private Cloud Compute. Can you mix all three within a single agentic flow? What are the data privacy and attribution boundaries once a third-party provider is in the loop?</h2><p>Yes, you can mix them in a single agentic flow. The key API is dynamic profiles, which lets you route different parts of a workflow to different models in a declarative way.</p><p>The panel described two patterns: baton pass and phone a friend.</p><p>In a baton-pass flow, each model receives the full context from earlier steps. That works well when all models have the same privacy expectations, such as staying on-device or within Private Cloud Compute, or when you are comfortable sharing the full context.</p><p>In a phone-a-friend flow, the main model calls another model with only the specific question it needs help with. The other model does not see the full transcript. It returns an answer, and control goes back to the original model. This is better when you want a stronger privacy boundary or when a third-party model should not receive all prior context.</p><p>Dynamic profiles and profile modifiers can also help manage different context windows. For example, you can keep only the last few transcript entries, drop old tool calls after they are used, or shape the transcript differently depending on which model is selected.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=982s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>My app uses on-device speech-to-text and must recognize names and proper nouns that general models miss. Does iOS automatically personalize recognition to each user, learning their words and pronunciations over time, or do I need to build and maintain that list myself?</h2><p>The panel did not have the exact speech framework owner present, so they did not give a definitive API answer.</p><p>At a high level, speech personalization often involves a component that adapts to user-specific words, names, contacts, or pronunciations. Whether the system API already exposes the personalization you need depends on the speech recognition API and language you are using.</p><p>The recommendation was to ask this on the developer forums so the speech framework engineers can answer directly.</p><p>If you need custom behavior beyond the system speech APIs, it may be possible to build your own model or personalization layer, but the panel suggested starting with native speech recognition support first, especially for supported languages.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=1322s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How do you train coding agents to know more about my code style or a specific area? I use a local LLM with Xcode or VS Code, and my complex codebase includes visionOS, Metal, physics simulation, and macros that generate 3D resources, but it does not perform very well.</h2><p>Agents learn best when they can search, inspect, and write down what they discover.</p><p>First, give the agent access to examples from your project. It will often infer style from nearby source code. For stronger guidance, use files such as <code>AGENTS.md</code> or similar project-level instructions. Keep the always-included file short, because it consumes context on every request.</p><p>Reference other markdown files from there. For example, tell the agent where the networking guide, style guide, Metal conventions, or macro documentation live. Then the agent can search those files only when needed.</p><p>You can also ask the agent to document what it learns. If it discovers how your networking layer works or how a crash was fixed, have it write that down so future sessions can reuse the knowledge.</p><p>For Xcode 27, the panel recommended trying ACP support. ACP lets Xcode talk to the agent of your choice, including agents connected to local models through tools like LM Studio or Ollama. This is more capable than a simple chat-completion workflow because an agent can manage state, use tools, inspect files, and work in longer loops.</p><p>The model still matters. Smaller local models can often copy style and follow local conventions, but they may struggle with deep reasoning across a large and complex codebase. Xcode&#8217;s documentation search tools can help ground the model in new Apple APIs during beta periods.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=1496s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>With regards to UI testing, what practical steps can teams take to integrate automated approaches into their testing workflows on Apple platforms?</h2><p>Think of testing in layers.</p><p>Start with small, fast unit tests. Agents are good at helping enumerate cases and generate many focused tests for small pieces of logic.</p><p>Then add a smaller number of integration tests that bring in dependencies and test broader behavior.</p><p>UI tests should be the final layer, not the only layer. They are more expensive and slower, so use them to verify the important connection points in the UI rather than every possible permutation.</p><p>Xcode 27 adds simulator interaction for agents. An agent can tap, swipe, type, inspect screenshots, and read the accessibility tree. That means it can explore an app, find issues, and then help generate repeatable UI tests so you do not need the agent to manually run the same exploration every time.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=2077s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Have there been any updates to Natural Language processing and Apple Vision? Now that Foundation Models support image attachments, what should be the preferred method of image extraction?</h2><p>If there is a specialized API for the task, use that first.</p><p>For well-understood image tasks such as barcode reading, OCR, segmentation, or detecting a known kind of object, Vision or image-understanding APIs are usually the right choice. They are efficient, specialized, and easier to test.</p><p>Foundation Models are better when the task requires semantic understanding, natural-language nuance, or open-ended interpretation. For example, if the user&#8217;s prompt changes the kind of image reasoning needed, an LLM or multimodal model may fit better.</p><p>The panel compared foundation models to a 3D printer: flexible and great for custom jobs. Specialized APIs are more like a production line: faster and better when the task is known and repeatable.</p><p>The same rule applies to translation. If you just need ordinary translation, use the Translation framework. If you need style transfer or unusual natural-language interpretation, then a language model may be appropriate.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=2261s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>On-device LLMs have relatively limited token capacity. What are the best practices for managing prompt size, tool definitions, and context to avoid exceeding limits while still maintaining high-quality responses?</h2><p>Use the token-counting and response-usage APIs in the Foundation Models framework. They let you inspect context size, count tokens, and see input, output, cached, and reasoning token usage.</p><p>When context grows too large, you have several options. You can drop old entries, drop tool calls and tool outputs once they have already been used, or summarize the conversation history.</p><p>Apple open-sourced Foundation Models utilities, including a summarize-history modifier. It can summarize the transcript once it exceeds a configurable size. The default prompt can be overridden, which matters because the right summary depends on your use case.</p><p>There is a tradeoff. Dropping or summarizing context can invalidate the KV cache and increase latency, but keeping too much context can distract the model or hurt accuracy. Use the Evaluations framework to compare strategies on the same dataset and see which one works best for your feature.</p><p>Also avoid asking one session to do too many unrelated tasks. If tasks are independent, split them into separate sessions so each task has a fresh context window.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=2500s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Foundation Models guardrails sometimes refuse emotionally intense but legitimate journal entries, such as grief or venting. Can I prevent refusals on first-person emotional writing, and how do I detect guard refusal versus other errors to fall back gracefully?</h2><p>There are two separate concepts: guardrail errors and model refusals.</p><p>For input moderation, the system language model supports a setting called permissive content transformations. If enabled, the model should not error out just because the input is emotionally intense.</p><p>However, the model may still refuse in natural language to continue or elaborate on certain content. That is a model response, not the same thing as a guardrail error.</p><p>If you are using structured output, the model can throw a refusal error. That is separate from a guardrail error, which comes from a moderation model checking input or output.</p><p>If the behavior does not match your legitimate use case, file feedback. Apple also noted that guardrails have been improved this year to reduce false positives.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=3213s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Apple has historically brought a distinct perspective to areas like design and privacy. What is your guiding philosophy or approach to AI evaluation?</h2><p>Evaluation should not happen only after an AI feature is built. It should start at the beginning.</p><p>The panel described evaluation as the living specification of an AI feature: the set of things the feature should do well, edge cases it should handle, and future headroom it should grow into.</p><p>This leads to an evaluation-driven development lifecycle. You start with a small curated dataset, expand it, run the model or configuration, inspect where it succeeds and fails, and then iterate.</p><p>The Evaluations framework is designed to make that cycle easier. It supports datasets, model judges, comparison across configurations, and workflows where developers can improve the feature instead of guessing.</p><p>The larger philosophy is that AI features are non-deterministic, so validation must be part of design and development from the start.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=3384s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is it possible for models used by different apps on iPhone to be shared across apps? This could help save storage space for users.</h2><p>In general, different apps cannot simply share one downloaded model across the system.</p><p>There are sandboxing, security, scheduling, and resource-management challenges. Even if two apps use what sounds like the same model, they may need different quantization, performance, quality, or memory tradeoffs based on their evaluations.</p><p>Apps from the same developer can share resources through an app group. Core AI model caching can also share resources inside a cache group when you have an app group.</p><p>But a system-wide shared model downloaded once and reused by unrelated apps is not available. If you can use the on-device Foundation Model built into the OS, that avoids increasing your app size and avoids shipping your own model weights.</p><p><a href="https://www.youtube.com/watch?v=bXb18GwYQS8&amp;t=3560s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical questions about coding intelligence, Foundation Models, Core AI, Core ML, MLX, evaluations, Vision, local models, Private Cloud Compute, Xcode agents, context management, guardrails, and model deployment.</p><p>Question acknowledgments: Jane Chow, Abi27, RB27, Desa, Indigo J, Claire Case, Pichaya_TR Yysy, Brian CM, John Lee, AO, and the online WWDC audience who submitted and upvoted the remaining questions.</p><p>Finally, a heartfelt thank-you to Shashank, Kevin, Eric, Stephen, Raziel, Angelos, and the teams behind the scenes for leading the session and explaining how Apple&#8217;s AI and machine learning tools fit together.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: Accessibility Technologies Group Lab   - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-accessibility-technologies</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-accessibility-technologies</guid><pubDate>Thu, 11 Jun 2026 15:02:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/98758107-3815-43c2-aa2b-dd1b70d57996_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Accessibility is not a final checklist item. It is part of how people actually experience software: reading, navigating, understanding, controlling, testing, and trusting that an app will work for them before they even download it. And for many this feature will bring whole world back again. I recommend you to watch this session.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>What is your best advice for testing usability? Accessibility Inspector to audit elements, Device Hub for testing across different screen sizes &#8212; any other advice?</h2><p>When testing accessibility, screen size and text size should be considered together. Dynamic Type and Large Text often affect layout just as much as a smaller or larger device does.</p><p>A view that works visually at the default font size can fall apart when a user enables an accessibility text size. Sometimes that means labels wrap. Sometimes it means the layout should change entirely so controls remain understandable and tappable.</p><p>So in addition to using Accessibility Inspector and Device Hub, test with larger text sizes early. Make sure your UI is dynamic enough to adapt to both different device sizes and different user text preferences.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=406s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>On most teams, accessibility testing means VoiceOver and then they call it a day. Switch Control, Voice Control, and Full Keyboard Access rarely get a pass. How does your team prioritize across assistive technologies when you cannot test everything? Which one do we most underestimate?</h2><p>The honest answer is that many teams are in exactly that situation. Not everyone has the resources to test every assistive technology deeply. Starting with VoiceOver is still a very good first step.</p><p>VoiceOver testing is approachable: you can turn it on quickly and begin testing without extra hardware. It also gives a lot of value because many assistive technologies share the same underlying accessibility information: labels, traits, elements, grouping, and structure.</p><p>If your VoiceOver experience is strong, Switch Control and Voice Control often benefit from that work too. There are additional APIs for polishing those experiences, but the shared accessibility foundation matters most.</p><p>The panel also emphasized the value of real users. You can get far by testing yourself, but feedback from people who rely on these technologies can reveal problems and expectations you may not notice.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=462s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there any way to override or turn off the system-provided description of an image? I have an app that displays artwork, and VoiceOver reads my provided description immediately followed by the system description. I am using the accessibility label modifier on a SwiftUI image.</h2><p>One possible technical workaround is to remove the image trait, which can stop VoiceOver from treating the element as an image and therefore stop the automatic image description behavior.</p><p>However, the panel cautioned against doing this too quickly. Removing the image trait may also prevent users from accessing newer image-description features, including the ability to ask follow-up questions about the image.</p><p>For artwork, the right answer can be nuanced. The artist-provided description may be the authoritative description, but an AI-generated description can still be useful for a blind user who wants a more literal sense of what is visually present.</p><p>VoiceOver plays a small sound before the generated description, so users can distinguish between your provided alt text and the system-generated description. That distinction may be enough in some cases, and preserving the image trait may give users more options.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=700s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m a third-year medical student building an EHR for underserved areas. Part of it is a patient portal. What new accessibility technologies and features announced at WWDC would be helpful for patients that I can fold into development now?</h2><p>Accessibility Reader is a strong fit for patient-facing long-form content. It is system-wide and lets users customize a reading experience that works for them, then apply that style to content from different apps.</p><p>Image descriptions and Image Explorer can also help when a portal contains charts, medical imagery, or data-rich visual content. These tools are not only for photographs; they can also help users understand charts and visual information.</p><p>More generally, start with Dynamic Type. A patient portal will likely contain dense and important text, so supporting larger text sizes well is a high-impact accessibility foundation.</p><p>After that, VoiceOver support gives another large win. Add useful labels, make sure navigation order makes sense, and test important patient workflows with VoiceOver enabled.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=826s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>In many places in my app, the user taps a button that triggers a network request. For sighted users I show a progress view in place of the button, then replace the button content. How do I make that accessible to iOS VoiceOver users? I&#8217;d like VoiceOver to announce when the action completes.</h2><p>Use accessibility notifications. For smaller layout changes, a layout-changed notification can tell VoiceOver that something in the UI changed. For larger transitions, a screen-changed notification may be more appropriate.</p><p>You can also pass an accessibility element to move VoiceOver focus, but do that carefully. Users do not want their focus pulled around unpredictably.</p><p>If the main need is simply to tell the user that the action completed, an announcement notification may be the clearest option. You provide a string and VoiceOver speaks it.</p><p>The key is to communicate the state change without making the experience noisy or stealing focus unnecessarily.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1014s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are there any updates to the text-to-speech APIs this year?</h2><p>The panel was not aware of new text-to-speech APIs in this release.</p><p>There are improvements on the backend, but no new developer-facing API was called out in the lab. Developers who need specific text-to-speech API changes should file feedback with concrete use cases.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1101s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Dynamic Type is now available on tvOS. What should tvOS developers think about, and what does the work look like to get ready?</h2><p>Start by learning the current best practices for large text. When accessibility font sizes are enabled, your layout may need to do more than scale text. It may need to reflow.</p><p>The panel specifically recommended the new WWDC session about Large Text on tvOS. Older Large Text sessions are also useful, even if they focus on iOS, because the same principles apply: avoid fixed assumptions, make room for content growth, and test with real accessibility text sizes.</p><p>Accessibility Nutrition Labels also now become relevant for tvOS in this area, because developers can report Large Text support for tvOS apps.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1134s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>We are very excited about Accessibility Nutrition Labels. Please talk about how valuable they are and any details you would like to expand on.</h2><p>Accessibility Nutrition Labels help users understand whether an app is likely to work for them before they download it. For many users, downloading an app without accessibility information can feel like gambling.</p><p>For developers, the labels are also a way to communicate work that might otherwise be invisible. If your team invested in VoiceOver, Large Text, subtitles, contrast, reduced motion, or other accessibility support, the label lets users see that commitment.</p><p>The panel also noted that the guidelines themselves are valuable. They give teams concrete criteria to evaluate against, which can help teams advocate internally for accessibility work.</p><p>One example shared was a developer at a large organization who used the label effort to help get support inside the company. That kind of visibility can turn accessibility from an afterthought into a visible product quality goal.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1204s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I was excited to see that there is a VoiceOver option in the Xcode 27 beta Device Hub, but when I tried to use it, it did not do anything. Is it intended to work on simulators or only on physical devices? Is this the year we can test iOS VoiceOver on a Mac?</h2><p>There is a new XCTest API that lets you test VoiceOver on an iOS device from a Mac. The panel believed it should also work in the simulator, but recommended taking specific failures to the forums and filing feedback.</p><p>The user already included a feedback number, and the panel said they would take note of it.</p><p>There is also an Accessibility forum Q&amp;A, which is a good place to bring detailed issues around Device Hub, XCTest, VoiceOver, and simulator behavior.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1419s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>We&#8217;re adding VoiceOver support to our macOS app. With this year&#8217;s accessibility updates, is there anything we should revisit? What&#8217;s the recommended way to test accessibility across different assistive technologies on Mac?</h2><p>On macOS, user expectations are different from iOS. Mac users often multitask, use more keyboard shortcuts, and expect faster navigation across sections of an app.</p><p>For VoiceOver on Mac, think beyond labels alone. Consider accelerators: keyboard shortcuts, ways to jump to sidebars, inspectors, and important regions, and workflows that let users move efficiently.</p><p>Many of these are not VoiceOver-specific APIs. They are general Mac app affordances that greatly improve the experience for VoiceOver users and keyboard-heavy users alike.</p><p>For testing, use the accessibility shortcut. On macOS, VoiceOver can be toggled with Command-F5. If you have Touch ID, triple-pressing Touch ID opens the accessibility shortcut. Without Touch ID, Command-Option-F5 opens it.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1499s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For custom AppKit controls with context menus, hover actions, and custom buttons, what is the right way to expose these so VoiceOver users can discover and perform the same actions as mouse users?</h2><p>Custom actions are often the default answer, but they have tradeoffs on the Mac because users may skip them or not expect them in every context.</p><p>If the actions are important, consider exposing them more explicitly. In SwiftUI, <code>accessibilityRepresentation</code> can let you expose a custom view as a different accessibility structure, such as multiple buttons instead of one complex visual control.</p><p>In AppKit, you can achieve similar results by implementing accessibility children and creating your own accessibility elements, though it is more manual.</p><p>Also consider keyboard shortcuts. If an action is useful for VoiceOver users, it may also be useful for power users and full-keyboard users. Be cautious with hover-only actions, because hidden UI can also be difficult for users with cognitive accessibility needs.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1678s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How does Apple design accessibility features for people with limited or no use of their hands or arms, including people with limb differences, paralysis, tremors, or temporary injuries?</h2><p>Apple provides a range of options because different users with similar disabilities may prefer very different solutions.</p><p>Examples include Voice Control, Head Tracking, Eye Tracking, Sound Actions, Switch Control, AssistiveTouch, Touch Accommodations, and Reachability. Some users may avoid touching the device entirely. Others may touch it, but need the device to interpret touch differently.</p><p>Touch Accommodations now has a new setup flow in iOS 27. Instead of manually tuning every setting, users can go through a calibration activity, tap targets, and receive a recommended configuration.</p><p>The panel also highlighted on-device eye tracking on iOS. External eye trackers can be very accurate, but they require hardware and setup. Built-in eye tracking can make everyday situations much easier, including something as simple as changing entertainment on an airplane without needing help.</p><p>The larger principle is flexibility: Apple avoids boxing users into one input model and instead provides multiple paths so people can configure the device around their needs.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=1813s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Could you mention some features made with neurodivergent people in mind?</h2><p>Guided Access and Assistive Access are two major features designed with cognitive accessibility in mind.</p><p>Guided Access helps keep someone inside a single app or experience. For example, it can let someone stay in a favorite book, TV show, or FaceTime call without accidentally leaving that context.</p><p>Assistive Access provides a simplified visual and interaction model. It uses larger default text, bigger touch targets, simplified app experiences, and a clearer visual structure. Apple has brought more first-party apps into Assistive Access, including the TV app.</p><p>There are also APIs for developers to create optimized versions of their apps for Assistive Access. Beyond those two features, Screen Time, Speak Screen, Speak Selection, and Accessibility Reader can also be helpful for neurodivergent users.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2047s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>SwiftUI has a new reorderable modifier. How do I make that accessible to VoiceOver? Dragging and dropping is not an idiom in VoiceOver.</h2><p>The panel did not give a final answer in the lab. They noted that the experience may vary by platform and recommended bringing this to the developer forums.</p><p>Drag-and-drop accessibility can differ between iOS and macOS. iOS drag and drop generally works well with VoiceOver, while macOS may have different behavior and expectations.</p><p>For now, this is a good case for forum discussion and feedback, especially if the new SwiftUI reorderable modifier does not expose the right accessibility interaction in the current beta.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2198s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I would love to learn more about the new FaceTime video interpreting feature announced at WWDC. How can third-party apps integrate? How is the interpreting session initiated, and are there entitlements or API restrictions?</h2><p>The feature is not available in the current iOS 27 beta, so the panel did not provide integration details.</p><p>The answer was to stay tuned for more information when the APIs become available.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2250s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For custom SwiftUI reusable views like labels, icons, and composite wrappers, what is the recommended way to expose stable accessibility identifiers for UI tests and Accessibility Inspector without misusing the accessibility label?</h2><p>Do not overload the accessibility label with test-only information. Labels are for users.</p><p>For reusable views, one practical pattern is to require both an accessibility label and an accessibility identifier when the wrapper is created. That makes the API push callers toward doing the right thing.</p><p>You can also add checks so the label cannot be nil. This helps ensure that future reuse of the component still includes meaningful accessibility information.</p><p>For symbols, icon buttons, and other reusable controls, require a real accessibility label and a stable identifier separately. The label supports assistive technology users, while the identifier supports testing.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2298s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>At the beginning of an app design project, what process do you recommend for making sure accessibility is built in from the start instead of bolted on later? How does that move from UX research and wireframing into Swift development, testing, and support for different assistive technologies?</h2><p>The fact that you are asking the question is already a strong start. Accessibility is much easier when considered from day one instead of taped onto the app at the end.</p><p>At the design stage, learn how users of different assistive technologies might use a similar app. If you have resources for usability research with disabled users, use them. Real users will find issues and suggest improvements you may not think of.</p><p>For larger teams, showing the real user impact can be powerful. Seeing a VoiceOver user touch a control and hear nothing creates a human connection that abstract requirements often do not.</p><p>Make accessibility part of the same planning conversation as security and privacy. It should be one of the dimensions considered throughout the project.</p><p>During implementation, shared components and wrappers can enforce accessibility labels and identifiers. During late-stage polish, reserve time to refine the VoiceOver and Dynamic Type experiences after the UI has stabilized.</p><p>Finally, do not only measure impact by the number of users. Also consider criticality. A feature may affect fewer users but be absolutely essential for those users to use the app at all.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2388s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>There are a lot of resources on accessibility for iOS, with fewer resources tailored to other targets. Are there platform-specific pitfalls to look out for when developing a universal app for macOS, iPadOS, watchOS, tvOS, and other platforms?</h2><p>A universal app still needs platform-specific testing. If an app works well on iOS, it may still need adjustments for iPad, Mac, TV, or Watch.</p><p>Accessibility is the same way. Test VoiceOver on iOS, but also test VoiceOver on macOS if you ship a Mac app. User expectations are different. Mac users may expect keyboard shortcuts, inspectors, sidebars, and fast navigation. iOS users may rely more on hit testing and touch exploration.</p><p>So the pitfall is assuming one accessibility pass covers every platform. Shared SwiftUI code helps, but each platform has different interaction models and different accessibility expectations.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=2799s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>With these new improved AI voices, will any of them be available for VoiceOver?</h2><p>VoiceOver voices are not just general-purpose system voices. They are specifically tuned for screen reader use, where people often listen at very high speech rates.</p><p>That means a voice that sounds natural in ordinary spoken content may not necessarily be the best fit for VoiceOver. The panel did not announce a simple &#8220;all new AI voices are available for VoiceOver&#8221; answer. Instead, they emphasized that VoiceOver speech has its own requirements and tuning.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=3199s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Besides manual testing, how do you prevent accessibility regressions? What about approaches like accessibility snapshot testing or automated UI testing with accessibility assertions?</h2><p>Automated UI testing is one of the strongest tools here because <code>XCTest</code> and <code>XCUITest</code> rely on the accessibility hierarchy across Apple platforms. If your accessibility hierarchy breaks badly, your UI tests may fail as a result.</p><p>You can also add explicit assertions for accessibility labels and other accessibility properties. For example, tests can check that important controls have labels and that the accessibility hierarchy exposes the elements your app depends on.</p><p>There is also a new VoiceOver testing API. It can start VoiceOver, navigate element by element, and validate what VoiceOver speaks or whether it reaches expected controls. That can be useful for smoke tests that verify VoiceOver can navigate key screen elements and does not encounter silent controls.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=3242s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m a blind iOS developer. Are there any accessibility improvements in Xcode and developer tools in general this year?</h2><p>Terminal accessibility has improved. VoiceOver can move to visible beginning, access marks better, and read tab-completion suggestions more reliably.</p><p>Xcode also has multiple VoiceOver bug fixes in the current seed. The panel noted that SwiftUI itself is a big accessibility win for blind developers because UI can be defined and edited in code, instead of relying on a visual interface builder workflow.</p><p>Xcode also has useful VoiceOver rotors for navigating code, including jumping between methods, errors, warnings, and variable names. The panel said there is still work to do and encouraged developers to keep filing feedback.</p><p>They also called out AI as a potential game changer for developer tools, especially because it can help summarize, inspect, and explain code in ways that may reduce the need to visually scan large files.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=3336s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m curious if you have recommendations for making RealityKit or 3D content accessible.</h2><p>RealityKit and spatial content need accessibility design too. The panel referenced prior WWDC material covering how to make RealityKit content accessible, including APIs for exposing spatial content to assistive technologies.</p><p>The main idea is that spatial or 3D content should not become invisible to users who rely on VoiceOver or other assistive technologies. You need to expose meaningful accessibility information for objects, interactions, and scene structure, not only render them visually.</p><p>The recommendation was to watch the dedicated session on making spatial content accessible, because it covers these APIs and design patterns in more detail.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=3565s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Assistive technology is for everyone. I use VoiceOver and Dictation all the time even without a disability.</h2><p>The panel highlighted this as an important point. Accessibility features often help far more people than the original audience they were designed for.</p><p>VoiceOver, Dictation, Large Text, captions, spoken content, and many other tools can be useful situationally, temporarily, or simply as a personal preference. This is one reason accessibility should be treated as core product quality, not a niche feature set.</p><p><a href="https://www.youtube.com/watch?v=1juOcrja4bo&amp;t=3648s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical, thoughtful accessibility questions throughout the session. Your questions made the discussion useful for developers working on VoiceOver, Dynamic Type, Switch Control, Voice Control, Accessibility Nutrition Labels, Device Hub, macOS accessibility, AppKit, SwiftUI, cognitive accessibility, mobility access, testing, and universal app design.</p><p>The uploaded transcript does not include stable attendee nicknames for most questions, so the acknowledgments avoid inventing names. Question acknowledgments: the online WWDC audience who submitted and upvoted the Accessibility Technologies questions.</p><p>Finally, a heartfelt thank-you to Cole, Julia Sonen, Drew, Greg, Sil, and the accessibility team behind the scenes for leading the session, sharing practical guidance, and explaining how to build accessibility into app experiences from the start.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: visionOS Group Lab  - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-visionos-group-lab-q-and-a</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-visionos-group-lab-q-and-a</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Thu, 11 Jun 2026 12:03:36 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/feb6cd3a-bdda-46b6-b08d-edf323e9fe6c_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>visionOS is still young enough that many of the best questions are not only about APIs. They are about workflows, debugging, 3D asset pipelines, spatial accessories, WebXR, visual intelligence, testing, and how to design experiences that feel native to Apple Vision Pro instead of simply ported from another platform.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>As an independent developer, what is the best way to gain access to the front-facing cameras for learning or trying something for an app? Do you need to be part of an enterprise development group, or is it impossible for an individual developer to gain access?</h2><p>Access to the main cameras on Apple Vision Pro exists, but it is handled through an application process and is primarily intended for professional and enterprise-oriented use cases.</p><p>It is not limited only to developers in the Apple Developer Enterprise Program. Developers with a standard Apple Developer Program account can apply as well, especially when the account is tied to a business and the app has a concrete use case that requires camera access.</p><p>If you have a real use case and cannot access the capability today, post in the developer forums and file feedback. Apple specifically asked developers to share use cases that would benefit from front-facing camera access, because that helps the team understand where broader access or different APIs may be needed.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=536s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Hitting a breakpoint in an immersive space means standing inside a frozen universe of your own making, squinting at Xcode through passthrough. How do you debug on device? Am I missing a trick?</h2><p>Use Mac Virtual Display inside the immersive environment. There is a developer setting that allows Mac Virtual Display to appear while you are in an immersive experience. That lets you keep Xcode visible while staying inside the app you are debugging.</p><p>This helps both productivity and safety. When a breakpoint hits, you do not need to leave the headset, remove the device, or try to inspect Xcode awkwardly through passthrough. You can keep the immersive context, inspect the breakpoint, and continue iterating.</p><p>The panel noted that this is also a common internal workflow: develop in Xcode through Mac Virtual Display while running the immersive content alongside it.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=666s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are any of the agent skills Apple is providing this week especially relevant for visionOS development?</h2><p>The most directly relevant skill is the one that helps resize an existing iOS app for visionOS. A 2D iOS interface usually assumes a particular form factor, density, and layout. On visionOS, those assumptions often need to change.</p><p>The skill is meant to help translate iOS UI into a form that better fits visionOS, while preserving platform best practices and principles. It is especially useful when bringing an existing iOS app to visionOS rather than starting from a fully spatial design.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=756s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is your usual workflow for exporting 3D models into USD? Some tools apply different transforms or remove certain materials on complex models. The usual models are FBX or GLB files.</h2><p>The recommended approach is to start with USD as early as possible in the pipeline. If you can export directly from the original 3D content creation tool into USD, that is usually better than transiting through another format first. Intermediate formats can leave their own &#8220;fingerprints&#8221; on transforms, materials, or scene structure.</p><p>There is still variability between 3D tools and how they export data, because the industry is still adopting USD at different levels of maturity. But starting from the original authoring tool and exporting USD directly gives you the best chance of preserving intent.</p><p>If you are iterating on models, consider loose USD formats such as USDA or USDC instead of repackaging an entire scene every time. That can make iteration faster because individual files can be modified without rebuilding the whole package.</p><p>The panel also noted that the USD core specification is now available, which makes it easier for modern AI tools to understand USD structure and potentially help inspect or repair model issues.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=813s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can spatial accessories be tracked outside the range of direct view? I&#8217;m imagining foot trackers, but obviously the user will not always be looking down at their feet.</h2><p>Spatial accessories use a combination of Bluetooth, an IMU, and infrared emitters that Apple Vision Pro can see with its infrared cameras. The IMU can continue providing motion information even when the accessory is briefly occluded or not perfectly visible.</p><p>That said, tracking fidelity depends on the accessory, distance, occlusion, size, and whether the Vision Pro cameras can see the infrared constellation. If an accessory is behind the body or far down near the feet, tracking quality may vary depending on the use case and required precision.</p><p>visionOS 27 opens the spatial accessory specification so developers and hardware makers can build new accessories. Companies such as DF Robot and MikroE were mentioned as providing components or boards that can be used to prototype accessories.</p><p>There is also a new debugging mode that shows the infrared feed Vision Pro sees, which helps you verify whether your accessory is visible in the positions you care about.</p><p>For foot-tracking use cases, Apple specifically asked for feedback explaining why lower-body tracking is needed and what the experience is trying to achieve.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=962s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When using Gaussian splatting, we notice that the splats are culled if you move your face close or inside the splat. We are trying to reconstruct an immersive scene through sensor data for telepresence. The culling is not ideal. Is there a way to disable this?</h2><p>There does not appear to be a way to disable that vignette or culling behavior today.</p><p>The behavior is not limited only to Gaussian splats. Similar treatment can apply to 3D content that gets too close to the user&#8217;s face, because the system tries to preserve an unobstructed field of view and avoid uncomfortable or unsafe experiences.</p><p>Apple asked developers with this kind of use case to file feedback. The panel emphasized that Gaussian splats are a new and evolving area, and concrete use cases are especially important. A telepresence scene is different from a small scanned object, so feedback should include the creative intent, sample project if possible, and screenshots or screen recordings showing the issue and distance where it happens.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1228s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is Gaussian splat rendering supported on platforms other than visionOS? The documentation shows support for iOS, but Xcode does not show the symbols available.</h2><p>Gaussian splatting should generally be supported across both iOS and visionOS. If the documentation and Xcode availability do not match, that is a beta issue worth filing as feedback.</p><p>The panel encouraged developers to file feedback when documentation says an API is available but Xcode symbols are missing or the feature does not work as expected. During beta, this kind of mismatch is exactly the sort of issue Apple can still investigate and correct.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1420s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Why aren&#8217;t there user profiles like on Mac? I want to put on the headset and choose who I am: owner, child one, child two, grandma.</h2><p>Apple Vision Pro is treated more like a deeply personal device, closer in spirit to an iPhone or iPad than a Mac with multiple user profiles. The device is calibrated to a primary user, including eyes and hands, and the experience is designed around that personal model.</p><p>That said, Apple has been reducing the friction of sharing. Guest User workflows have improved, and enrollment can be saved to an iPhone. After someone completes enrollment once, they can store it on their iOS device, and the next time they use another Vision Pro in Guest User mode, the phone can present an App Clip code to restore that setup quickly.</p><p>There is also Guest User with nearby device, where an iPhone or iPad paired to the same iCloud account can help approve access and mirror what the guest sees. So while full Mac-style profiles are not the model today, sharing workflows have become much faster.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1473s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there any API to detect the lips and face of the owner and mimic that on 3D, like Persona but differently?</h2><p>The ARKit face tracking API is iOS-only. visionOS does not provide raw face or lip-tracking data to third-party apps in the same way.</p><p>Third-party apps can access a camera-style video feed containing the user&#8217;s Persona for use cases like video conferencing, but not raw face geometry or data that would let you build custom lens-style effects or drive a different 3D face directly.</p><p>The panel said this is an interesting use case and recommended filing feedback with details about what you want to build.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1620s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For visual intelligence on Apple Vision Pro, when Siri answers questions about content the user is looking at, either app windows or real-world objects, does a third-party app need access to the main camera, or is visual intelligence handled entirely at the system level?</h2><p>Visual intelligence is handled at the system level. Third-party apps do not need camera access for this capability, and the information is not shared with third-party apps.</p><p>The panel emphasized that this is done in a privacy-preserving way. The system can answer questions about what the user is looking at, but that does not mean an app receives the raw camera feed or scene data.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1692s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For apps that support game controllers but are not games, how does Apple envision controllers fitting into the long-term interaction model of visionOS alongside eyes, hands, and voice? Are there new controller capabilities in visionOS 27 that developers should design for today?</h2><p>The input model depends on the experience. Eyes and hands remain fundamental because they map naturally to how people interact with the physical world: you look at something, then reach or act on it. Voice also fits many workflows.</p><p>Controllers and spatial accessories are useful when they add something beyond natural input: precision, haptics, physical affordances, repeatable controls, or specialized interaction. Examples include a stylus-like accessory for refined input, PlayStation VR2 Sense controllers for games, traditional game controllers, or custom spatial accessories built with the newly opened specification.</p><p>visionOS 27 expands this direction by opening spatial accessory support so developers can build custom tracked hardware. These accessories can integrate at the system and SDK level, while still fitting into the broader visionOS interaction model.</p><p>The guidance is not to use a controller just because it exists. Use it when it meaningfully improves the experience.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=1748s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are there any updates to WebXR in Safari with visionOS 27? In particular, immersive AR mode. We released a WebXR app and would love to run it with immersive AR mode enabled, but we are currently limited to immersive VR only.</h2><p>Safari on visionOS supports WebXR, but the panel described it as immersive-only rendering mode today.</p><p>For AR-style experiences, Apple encouraged developers to also look at the native platform APIs, because visionOS has a rich set of APIs for spatial and augmented reality experiences. Those APIs may allow a deeper integration than the current WebXR path.</p><p>The panel also mentioned web-side improvements such as the model tag, which lets web pages surface 3D content and personal environments without using WebXR in the same way.</p><p>If a WebXR app needs a specific AR capability, the recommendation is to file feedback and include the app link, desired behavior, and concrete use case.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2009s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For visual intelligence on visionOS, is it purely camera-based, or does it leverage the depth sensors under the hood?</h2><p>The panel did not disclose the exact implementation details. The high-level answer is that Apple aims to provide the best possible result on each platform using the sensors available on that device.</p><p>Different devices have different cameras and sensors, so there is not necessarily one identical implementation everywhere. The system can blend data from available sources depending on the device and the context of the query.</p><p>The same general principle applies across visionOS features such as mapping, hand tracking, and spatial understanding: the system uses the sensor data that makes sense for the experience while preserving the platform&#8217;s privacy and interaction model.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2170s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>The device sleeps the instant it leaves your head, taking the debug session with it. What is the sanctioned way to keep it awake on a desk during development?</h2><p>There is no &#8220;keep awake on desk&#8221; mode equivalent to caffeinating another device. The reason is security: taking Vision Pro off is effectively the lock gesture. The expectation is that the next person who puts it on must authenticate with Optic ID or passcode.</p><p>The practical workflow is to use Mac Virtual Display while wearing the device. That keeps Xcode, SwiftUI previews, and Reality Composer Pro workflows available in headset while you build and iterate.</p><p>If you do need to remove the headset, having Optic ID configured makes returning much faster. The panel described Mac Virtual Display plus Optic ID as the common development workflow.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2265s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Can you give us an update on automated testing for visionOS, ARKit, and RealityKit?</h2><p>For RealityKit, look at <code>RealityRenderer</code>. It can drive RealityKit rendering programmatically and can be useful for automated tests or other non-interactive rendering workflows.</p><p>The panel also recommended new testing sessions, including &#8220;Migrate to Swift Testing&#8221; and &#8220;Get the most out of Device Hub.&#8221; Device Hub can support more testing workflows than many developers may expect.</p><p>RealityKit is written in Swift, so existing Swift testing and Xcode testing infrastructure should also apply where appropriate.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2359s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the biggest mistakes new visionOS developers make?</h2><p>One major mistake is assuming that an app that works in the simulator will feel right on device. On Vision Pro, information density, window size, tap target size, and interaction style feel very different because users interact with eyes and hands in a spatial environment.</p><p>Another mistake is stopping at a direct 2D port. A good visionOS app should consider what is uniquely possible on Vision Pro. For an e-commerce app, that might mean letting users pull a 3D product model out of the window and place it on a desk or floor. For other apps, it might mean using room understanding, lighting, spatial placement, or immersive presentation.</p><p>The panel compared this to the early iPhone transition. Early apps often copied old interaction models, but the most interesting apps eventually embraced touch as a new medium. visionOS is at a similar stage: developers should explore what spatial computing makes newly possible rather than only reproducing existing UI patterns.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2450s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Regarding agentic coding in visionOS, I tried agentic coding in Xcode to generate a Vision Pro visionOS project &#8212; just a Hello World entity in a <code>RealityView</code>, following a local LLM WWDC26 session &#8212; but it generated the app with wrong syntax. Is there any way to fix this or teach the AI to know more about visionOS code?</h2><p>Generated code can be wrong, especially when APIs are new and models were trained on older versions of frameworks. The first recommendation is to be deliberate with prompts and always verify the generated output. Treat it as &#8220;trust, but verify.&#8221;</p><p>For local models, Apple&#8217;s sample projects can help provide better context. Examples mentioned include Hello World, Petite Asteroids, Canyon Crosser, and the newer Model Manipulator sample. Feeding or referencing strong sample code can help the model produce more accurate visionOS and RealityKit patterns.</p><p>The panel also noted that some local LLM experiments can work surprisingly well, but results will vary. visionOS APIs move quickly, so there may be a transition period where models lag behind the latest SDK syntax. Smaller prompts, real sample projects, and compile-checking the output are the practical workflow.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=2840s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For Object Tracking, how fast it can get now? What will be possible frame rate and latency?</h2><p>visionOS 27 improves object tracking in a few important ways. First, the Object Tracking tool in Create ML has been improved so it can support more kinds of shapes. The earlier version worked best when objects had enough visual features and clear geometry; the new version can handle a wider range of objects.</p><p>Second, RealityKit and ARKit now align object-tracking updates with frame display time. That matters when you are rendering content against a tracked object, because you no longer have to guess whether the tracking update corresponds to the previous frame or the next frame. The tracked pose is aligned with the actual frame being presented.</p><p>The practical advice is to use the new tool, test with the real object, and file feedback with sample assets or recordings when tracking quality is not good enough. For larger or awkward physical objects, it may still be better to track a smaller attached feature or use a spatial accessory.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=3045s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Vision Pro has awesome sensors and impressive frameworks like Persona Kit and CoreIK running under the hood. Will Apple expose more of these to developers, particularly for accessibility-focused telepresence apps for disabled users working in hybrid environments?</h2><p>Apple did not announce new access to those lower-level internal capabilities in this lab. The answer was to file feedback with the exact accessibility and telepresence use case.</p><p>The panel emphasized that Apple cares deeply about accessibility, and that concrete use cases matter. A general request for access to internal frameworks is less useful than a clear explanation of the user need, what you are trying to build, what data or capability is missing, and why current public APIs cannot solve it.</p><p>The discussion also noted that accessibility features often help many more people than the original target group. So if the request is tied to disabled users, hybrid work, or telepresence, it is especially worth documenting carefully in Feedback Assistant.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=3208s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there any way to use Mac Virtual Display with a shared Mac or an enterprise Mac setup, especially where the Apple ID on the Mac and Vision Pro may not match?</h2><p>The recommended path is to file feedback for the exact enterprise or shared-device scenario. Mac Virtual Display is designed around a personal workflow, and some enterprise environments introduce constraints around Apple IDs, device ownership, and managed hardware.</p><p>The panel did not give a workaround or announce a new capability here. The key point was that Apple needs concrete details: whether this is a shared lab Mac, a managed enterprise Mac, a device used by multiple employees, or a training/development setup. Those details help the team understand what kind of support would actually solve the problem.</p><p>For day-to-day development today, the strongest recommended workflow remains Mac Virtual Display with the developer&#8217;s own Mac and Vision Pro, especially when debugging immersive content.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=3325s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are Gaussian splats supported in USDZ files, and can they be distributed through the same workflows as other USD content?</h2><p>Gaussian splats are tied to USD workflows, but the exact packaging and support details depend on the current beta tools and APIs. The panel&#8217;s broader recommendation was to file feedback when documentation, Xcode symbols, or asset behavior do not line up with what you expect.</p><p>Because Gaussian splatting is still a new area for RealityKit and visionOS, concrete examples matter. If a USDZ packaging workflow does not work, include the asset, the expected result, the observed result, and any sample project needed to reproduce the issue.</p><p>This is especially important during beta, when format support, tooling behavior, and documentation can still be clarified or corrected.</p><p><a href="https://www.youtube.com/watch?v=NWwrdciI74M&amp;t=3565s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>&#127942; Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared thoughtful visionOS questions throughout the session. Your questions made the discussion useful for developers working on camera access, debugging workflows, USD pipelines, spatial accessories, Gaussian splats, visual intelligence, WebXR, testing, native visionOS design, agentic coding, object tracking, accessibility, travel scenarios, and thermal behavior.</p><p>Question acknowledgments: Wes Matlock, Thomas Bastible, Jodor Tree, Sismeia, Eric, Ethan, Oliver Cologne, Dilliam, Raymond Yay, and the online WWDC audience who submitted and upvoted the remaining questions.</p><p>Finally, a heartfelt thank-you to Adesh Pavani, Katie, Norman, John, Matt, and Travis for leading the session, sharing practical guidance, and explaining how to think about visionOS as a spatial platform rather than just another screen.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: Power and Performance Group Lab - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-power-and-performance-group</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-power-and-performance-group</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Thu, 11 Jun 2026 09:02:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7dc00a29-3b74-4e42-9615-2f69cdfec7d2_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Power and performance work is easy to postpone until the app feels slow, drains battery, or starts showing bad field metrics. But the best answers from this lab all point in the same direction: measure first, understand the real bottleneck, and optimize the parts that users actually experience.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>As a beginner in iOS development, what are the main factors that affect app power usage and performance in SwiftUI, and how can I design simple apps that avoid battery drain and lag?</h2><p>For SwiftUI, start by separating views from their inputs. Make sure views are only watching the state they actually depend on. If a variable changes but a view does not visually depend on it, that change should not cause unnecessary redraws.</p><p><code>@Observable</code> helps here because SwiftUI tracks only the fields a view reads. That gives you a cleaner dependency model and helps avoid updates for data the view does not care about.</p><p>Use Instruments to confirm what is really happening. The SwiftUI instrument can show view updates and dependency relationships, including cause-and-effect graphs. If the issue is power-related, use Power Profiler as well. SwiftUI performance work often improves power too, because reducing unnecessary computation also reduces energy use.</p><p>But do not assume SwiftUI is the problem. If you are new to performance work, start with the overall picture. Your battery drain might come from an algorithm, file I/O, networking, location, or background work. Measure first, then decide where to optimize.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=366s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the biggest power mistake you see in many apps that developers don&#8217;t realize they&#8217;re making?</h2><p>One of the biggest mistakes is not having enough telemetry or instrumentation. Without measurement, developers often optimize the wrong thing. A single customer report can point to one issue, while field analytics may reveal a broader and more important power problem.</p><p>Another common issue is not accounting for different app states and data sizes. A database write might look fine during local testing but become expensive for users with much larger datasets. That can show up in Xcode Organizer energy logs even if you never reproduce it at your desk.</p><p>You also need to remember that everything uses power: file system access, network requests, location updates, CPU work, GPU work, and display usage. If you can do less work, batch work better, or make network requests half as often, that can be a direct power win.</p><p>Use real-world profiling where possible. Power Profiler supports untethered workflows, where you record traces on-device for longer periods and analyze them later. That helps capture behavior that may not appear in a controlled desk setup.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=728s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the best documentation for Instruments? Are there any written guides?</h2><p>The recommended starting point is the Instruments tutorials. They were written by an Instruments engineer and are designed to feel like someone is walking you through the app step by step.</p><p>The tutorials include an associated project with built-in performance issues, so you can learn the full loop: profile the app, identify hangs or slow paths, understand how they appear visually, fix them, and then verify the improvement.</p><p>For beginners, Time Profiler with the flame graph view is also a good first tool. Flame graphs make it easier to see where time is going because the expensive call stacks are visually obvious. Top Functions is another useful view because it flattens the call tree and shows expensive helper, compiler, and runtime functions more directly.</p><p>If documentation is missing for a specific workflow or instrument, the panel encouraged developers to file feedback.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=916s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Our app shows UIKit and SwiftUI screens without much background work, but it is still using high battery according to Xcode. What are the best practices to know what is going on and why this might be happening?</h2><p>First, distinguish foreground energy from background energy. UIKit and SwiftUI work should usually show up as foreground energy. If you are seeing background energy drain, look for background tasks, location work, networking, or other work scheduled while the app is not in the foreground.</p><p>Power Profiler is a good starting point, especially in untethered mode. Record a trace on the device while using the app in realistic conditions, then bring the trace back to the Mac and inspect it in Instruments.</p><p>Power Profiler can break down energy use by subsystem, such as CPU, GPU, display, networking, and other sources. That helps narrow the issue. For example, high display energy may come from brightness or frequent visual changes, while high network energy may point to background requests.</p><p>Do not rely only on what you can reproduce while tethered to Xcode. Some power problems only appear when the app is used in real-world conditions.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=1060s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>A theme object like colors and tokens is injected through <code>EnvironmentObject</code>and read in every atomic component, dozens of nesting levels, hundreds of components on screen. At what scale does this become a bottleneck, and is there a recommended alternative?</h2><p>There is no single numeric threshold. The cost depends on what the views are doing, how often the environment value changes, and how much downstream work those changes cause.</p><p>Putting values in the environment can be a useful abstraction for passing data down a view tree. The important question is whether changing that environment value causes a large amount of unnecessary invalidation.</p><p>Use the SwiftUI instrument to see the downstream effect of those changes. It can help you understand whether the environment object is actually causing expensive updates or whether it is fine in your specific case.</p><p>If the object changes frequently and many views read it, you may need to split the data, move rapidly changing values closer to the leaf views that need them, or reduce the amount of state that is globally injected.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=1208s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How would you load large data sets like 50,000 or 500,000 records in SwiftUI tables, and how would you analyze performance and regressions using MetricKit?</h2><p>Start by asking how much data the user experience actually needs to load at once. If the UI is not displaying all 500,000 records, loading everything immediately is probably unnecessary work.</p><p>Load only what you need. Use batching, pagination, lazy containers, and model-side filtering so SwiftUI receives the data it actually needs to display. SwiftUI has lazy stacks and lists that can help load views as they come on screen.</p><p>For MetricKit, use state reporting to understand what the app was doing when a metric changed. Rather than logging a rapidly changing number like the exact item count on every update, group states into meaningful categories such as small, medium, and large batch sizes. That gives you useful slices of performance data without creating excessive logging overhead.</p><p>This lets you compare how different loading strategies behave in the field, and it helps you decide where to invest optimization effort.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=1288s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How does iOS 27 prioritize background tasks when the system is under heavy Apple Intelligence workloads?</h2><p>It depends on which system resources are being used. Many Apple Intelligence workloads run on the Neural Engine or in Private Cloud Compute. If your app is using CPU while the intelligence workload is using a different resource, they may be able to run at the same time without much conflict.</p><p>Still, background tasks should be designed in small chunks. If the system needs to pause or delay work, your task should be able to resume without starting over from the beginning. That makes progress more reliable even when the system is busy.</p><p>Instruments also adds more visibility here. System Trace can show thread priorities and help you understand whether you are assigning QoS incorrectly, or whether another task preempted your thread.</p><p>The general principle is that the system tries to schedule work automatically to preserve the best user experience. Your job is to make background work resumable, appropriately prioritized, and not overly monolithic.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=1523s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>To avoid blocking the main thread, I perform expensive tasks on background threads. During launch this causes a lot of thread hops. How expensive is frequent thread hopping compared to thread blocking, and is there a better solution for performance?</h2><p>Thread hopping has overhead, but if you are not doing it excessively, that overhead is usually small. It becomes a concern when it happens extremely frequently.</p><p>The bigger issue during launch is often doing too much work too early, or starting background work and then making the main thread wait for it before the first frame. That still blocks the user experience even if the work happens on background threads.</p><p>Focus on the minimum data needed to draw the first useful frame. Defer non-critical work until after launch. Cache previous results when possible. For example, if you fetch A/B test configuration during launch, consider using the cached result first and updating later.</p><p>Use Instruments to measure the problem. System Trace can show context switch counts and blocked thread states. Xcode Organizer can also show real-world launch metrics and call stacks for slow launches in shipped apps.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=1688s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How should developers measure app launch time? Should they inspect low-level APIs to see when the process was created?</h2><p>Use Apple&#8217;s tools instead of trying to infer launch time yourself from low-level APIs. MetricKit and Xcode Organizer measure launch efficiently and include parts of launch that your app process cannot measure on its own.</p><p>For example, they can account for the interval from when the user taps the app icon to when the first rendered screen appears. Your app code cannot directly measure the moment before your process exists.</p><p>This is the same kind of launch measurement Apple uses for its own apps. It avoids adding extra measurement overhead and gives you a user-focused view of launch performance.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2045s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Besides background tasks, what is the most common silent battery killer in SwiftUI? How much power does view over-invalidation actually drain, and what is the best Instruments workflow to catch it before release?</h2><p>View over-invalidation can be a serious silent battery issue because it may not produce visible changes. The screen can look exactly the same while the CPU keeps recreating views in the background.</p><p>The key is to minimize unnecessary redraws. Keep dependencies precise, flatten overly deep hierarchies where appropriate, and avoid repeatedly dispatching background work just to fetch data for UI that could have been cached or computed once.</p><p>Use the SwiftUI instrument to find unnecessary view updates. Pair that with Power Profiler to understand the energy cost. If the extra invalidation is CPU-heavy, it can show up as foreground energy drain.</p><p>The larger principle still applies: cache when possible, avoid repetitive work, and measure before optimizing.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2148s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are there performance improvements for very large lists and table views in SwiftUI for macOS 27? What are best practices for making long SwiftUI lists and tables work well with tens or hundreds of thousands of items?</h2><p>For very large lists, keep the list structure as stable as possible. If cells constantly change size, that can force SwiftUI to recalculate what appears at each scroll offset. Stable row sizes are easier for SwiftUI to cache and reason about.</p><p>Use lazy data structures and lazy SwiftUI containers where appropriate. Also move filtering and data preparation into the model layer rather than putting conditionals and transformations directly inside <code>ForEach</code>. Give SwiftUI the final data it needs to display, not a giant unfiltered set it has to chew through during rendering.</p><p>Batch work and load only the visible or near-visible data. The same idea used for launch performance applies here: what is the minimum information needed for the next useful UI update?</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2240s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I use <code>AnyView</code> for type erasure in several places. How expensive is it in practice? Is there a concrete threshold beyond which <code>AnyView</code> becomes a problem, or is avoiding it premature optimization?</h2><p><code>AnyView</code> and type erasure do add some overhead when creating views, so it is reasonable to avoid them when they are easy to avoid.</p><p>But do not contort your design just to remove <code>AnyView</code> everywhere. If it becomes measurable, then address it. Otherwise, you may spend time optimizing something that does not matter for your app.</p><p>This is a general performance principle: measure first. Some APIs have costs, but they exist for a reason. If using a type-erased view makes a design cleaner and does not show up in profiling, the time may be better spent elsewhere.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2383s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>MetricKit state reporting and crash report extensions are new and noteworthy this year. Are there any other headlining developer tools worth looking at?</h2><p>Xcode Organizer received important improvements, especially around Metric Goals. Metric Goals help you understand how your app compares to similar apps, which is useful because raw numbers are often hard to judge in isolation.</p><p>For example, a video app may naturally use more power than a simple utility app. Metric Goals give you a more meaningful baseline by comparing against apps in similar categories.</p><p>State reporting is also worth adopting. Combined with MetricKit, it lets you slice metrics by app state and identify where power, launches, hitches, or hangs are poor. That gives you a much clearer target for optimization.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2518s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>If there is one thing developers should try from the new tools this year, what should it be?</h2><p>Metric Goals are a strong starting point because they show how your metrics compare with similar apps. That helps you understand whether power, launch time, hitches, or hangs are genuinely worse than expected.</p><p>State reporting is the second big one. It lets you connect metrics to specific app states, so you can see where performance or power is getting worse. Together, Metric Goals and state reporting help move performance work from vague concern to targeted investigation.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2725s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Swift performance is something I&#8217;m genuinely passionate about and want to go deep on beyond WWDC sessions and Swift Evolution proposals. What does the pathway look like for building real deep expertise?</h2><p>Experience is the main path. Build things, profile them, and inspect what actually happens. Instruments gives you many perspectives, including Time Profiler, SwiftUI instrument, and other templates that reveal what frameworks are doing behind the scenes.</p><p>For deeper Swift performance expertise, study how the standard library and open-source Swift packages are implemented. Look at benchmarks, inspect generated behavior, and experiment with changes. Combine that with Instruments so your mental model is grounded in measurements, not assumptions.</p><p>The important habit is to form hypotheses and then test them. Swift performance work is not only about knowing APIs. It is about understanding where time and energy are actually spent, then using the right tool or code change to improve the real bottleneck.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=2808s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>If I have a newer device but want to make sure performance is good on older devices, what should I do?</h2><p>A simple first step is to turn on Low Power Mode. It reduces CPU performance to save battery, which can reveal issues that you may not see on a newer device running at full performance.</p><p>Use physical devices for profiling when possible. Simulator performance depends on the Mac, not the simulated device model, so it is not a reliable way to judge device performance.</p><p>Condition inducers can also help. They let you artificially induce system conditions such as thermal pressure without physically heating the device. Field data from MetricKit and Xcode Organizer is also valuable because it shows performance across real users, real devices, and real conditions.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=3163s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Does using a lot of <code>@Environment</code> properties in a large SwiftUI app have any performance impact?</h2><p>Reading values from the environment is not necessarily expensive by itself. The main performance issue is environment churn.</p><p>If values are placed in the environment and read by many views, that can be fine when they do not change frequently. Problems start when those environment values update often. Then every view that reads from the environment has to reevaluate whether something changed.</p><p>So the issue is not simply &#8220;many environment values.&#8221; The issue is frequently changing environment values across a deep view hierarchy. Use the SwiftUI instrument to verify whether that churn is actually causing rendering or update performance issues.</p><p><a href="https://www.youtube.com/watch?v=Lhv33ipQERc&amp;t=3542s">&#9201;&#65039;Time code</a></p><div><hr></div><h2><code>&#127942;</code>Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared practical, performance-focused questions throughout the session. Your questions made the discussion useful for developers working on launch time, SwiftUI rendering, background work, MetricKit, Xcode Organizer, Instruments, power usage, and real-world field diagnostics.</p><p>The uploaded transcript does not include stable attendee nicknames for most questions, so the acknowledgments avoid inventing names. Question acknowledgments: the online WWDC audience who submitted and upvoted the Power and Performance questions.</p><p>Finally, a heartfelt thank-you to Cole, Terry, Yanni, Kasper, Kunal, and Marco for leading the session, sharing practical guidance, and explaining how to approach power and performance with measurement-first thinking.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[ WWDC26: SwiftUI for Beginners Group Lab - Q&A]]></title><description><![CDATA[Direct answers from Apple Engineers during WWDC26]]></description><link>https://antongubarenko.substack.com/p/wwdc26-swiftui-for-beginners-group</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-swiftui-for-beginners-group</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Thu, 11 Jun 2026 06:01:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d5ddef3f-b001-41e4-b6cd-14065acbc01c_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Next Lab in the series. This time, it&#8217;s a discussion of basic (and not-so-basic) questions about SwiftUI.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>As a beginner, I&#8217;m confused. Should I use React Native or SwiftUI? Both are incredible, but I think React Native allows me to release on both platforms. Can you introduce some of the advantages of what SwiftUI can do?</h2><p>One great advantage of SwiftUI is that it is closely tied to all of Apple&#8217;s platforms. When Apple introduces a new design language, such as Liquid Glass, you get a lot of that automatically by using the native UI framework in your apps. SwiftUI also evolves over time to take advantage of new features in Apple devices and operating systems.</p><p>A cross-platform framework can look efficient at the beginning, but it may leave you in an awkward middle spot later, especially when platform design directions diverge. Developers may end up doing more work near the final polish stage, when adapting is harder. Native frameworks can make that final stage easier because they naturally follow the platform.</p><p>SwiftUI is also built on Swift, which is a performant and expressive language. By choosing SwiftUI, you get the benefit of the native framework and the Swift language together.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=510s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>As an accounting student with zero knowledge in coding, what&#8217;s the best approach to learn coding and building an app with SwiftUI?</h2><p>A good approach is to start with your own life. Think about one problem you encounter every day, or something that could help your family or friends. That gives you an idea that motivates you, and it gives you something useful to share and test with real people.</p><p>Small personal apps are often the best starting point. The panel shared examples like a Pomodoro timer, a doorbell-sound app to get dogs off the bed, a to-do list, a ping-pong score tracker, and an app for color blindness. Each one started from a concrete personal need.</p><p>With agentic coding in Xcode, you can build faster and see ideas come alive on screen quickly. But the important part is still choosing a problem that matters to you. That motivation helps you keep going and makes the learning process more practical.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=661s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m a CS student just starting iOS development. If your goal were to become a highly skilled, job-ready iOS engineer in 2026, what roadmap would you follow from Swift fundamentals to advanced app development? Which skills would you prioritize most?</h2><p>Start with Swift. It is the basic building block for iOS development, and it may feel different if you are coming from another language. Spend time with the type system, concurrency, and the language features that help Swift enforce safe habits from the beginning.</p><p>After that, start building with SwiftUI. Pick a basic app that is fun for you and work through the official interactive SwiftUI tutorials. The panel specifically called out the online tutorials as a strong introduction because they walk through views, state, animations, and data flow.</p><p>Use agentic tools as part of your learning, but do not treat them as a replacement for learning. Look at the code they produce, ask why it works, ask what could be improved, and use them as tutors. You can also write code yourself and ask the agent for a review. That can help you discover APIs, patterns, and conceptual gaps.</p><p>A job-ready iOS engineer also needs skills beyond UI: networking, data storage, app architecture, debugging, and understanding how pieces fit together. Let the app you are building reveal what you need to learn next.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=894s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For a beginner coming to SwiftUI, what exactly happens under the hood when I use <code>@State</code>? Why can&#8217;t I just use a standard Swift <code>var</code> for data displayed in the UI?</h2><p>SwiftUI views are meant to be lightweight descriptions of what should appear on screen, not long-lived objects that you mutate over time. But apps still need somewhere to store information that changes, such as a counter or rating. <code>@State</code> tells SwiftUI to own that value and keep persistent storage for it behind the scenes.</p><p>SwiftUI can recreate your view many times as parent views update and as your app changes. A regular stored property on the view struct can be thrown away when the view is recreated. <code>@State</code> lets SwiftUI keep the value alive and provide it back to the view each time the view is rebuilt.</p><p>When the state changes, SwiftUI knows that the UI depending on that state needs to update. This is why <code>@State</code> is not just a variable. It is a way to connect mutable view-local data to SwiftUI&#8217;s update system.</p><p>The Swift compiler helps here too. If you try to mutate a normal variable inside a view body, you will get a compiler error. That is one of the advantages of SwiftUI being embedded in Swift: many mistakes are caught at compile time instead of becoming runtime bugs.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=1135s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m primarily a web designer with knowledge only in HTML, CSS, some PHP, some JavaScript, and some Lua. I&#8217;m interested in learning to develop apps for Apple platforms. How easy would it be for someone with little to no traditional coding knowledge or skills to get started with Swift and SwiftUI?</h2><p>If you already have experience with HTML, CSS, JavaScript, Lua, or PHP, do not underestimate that knowledge. It is already a meaningful starting point. Swift has similarities to other C-like languages, so many syntax concepts will feel familiar.</p><p>SwiftUI may also feel natural to people coming from the web because it has a declarative structure. The hierarchy you see in SwiftUI code is similar in spirit to how HTML describes a page. You describe what you want the UI to be, and the framework builds it.</p><p>Agentic tools can also help translate your existing mental model. You can ask how something you understand from one environment maps to idiomatic Swift or SwiftUI. That back-and-forth can make the transition easier.</p><p>One difference from web development is the compile-and-run cycle. If you are used to browser updates as you edit, use Xcode previews. Previews, simulators, and device mirroring can help you see changes quickly while learning.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=1386s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Where should I start? Especially in SwiftUI, there are so many tutorials, even on the Apple Developer side, and it is a little bit confusing what to do first. Where should I begin? I&#8217;m a Java developer and want to begin with iOS programming.</h2><p>Start with Apple&#8217;s official SwiftUI tutorials. They walk you through the basics, from what a view body is, to state, animations, data flow, and building apps that become more production-ready. These tutorials are a strong first foundation.</p><p>Community resources can also help. The panel mentioned Paul Hudson&#8217;s 100 Days of SwiftUI as a useful bite-sized learning path, along with books and community articles.</p><p>But the most effective path is not only doing tutorials in order. Pick something you want to build and let that guide what you learn. If your app needs search, storage, navigation, or animations, that need gives you a reason to learn those tools.</p><p>The Human Interface Guidelines are also worth reading early. They help you understand the design concepts common to Apple platforms and make your app feel at home. Another good exercise is to study apps you like and ask: how would I build that interaction in SwiftUI?</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=1622s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the best practices in SwiftUI to handle frequent real-time state updates, like sensor data, without causing unnecessary view redraws or lagging the UI?</h2><p>There are several tools available, and the right answer depends on the kind and frequency of the data. For data that updates very frequently, <code>@Observable</code> is a good tool because SwiftUI can update only the views that depend on the changed properties.</p><p>Keep dependencies limited. Avoid updating values that are read in many places at a high frequency. Keep view bodies small and push rapidly changing UI into leaf views when possible. If you have a series of content, tools such as <code>TimelineView</code>, lazy stacks, <code>ForEach</code>, and <code>List</code> can help SwiftUI avoid unnecessary work.</p><p>Ask whether the UI really needs to update for every data point. Sometimes the data model can compute a semantic state, such as one of three display states, and the view only needs to update when that state changes. That can be much cheaper than redrawing for every sensor value.</p><p>Avoid placing frequently changing values in the environment, because that can invalidate many downstream views. Keep async work outside the view body when possible, and feed SwiftUI synchronous inputs that are easier to reason about and animate smoothly.</p><p>Also, do not over-optimize too early. SwiftUI manages a lot of this for you. Start naturally, profile when you see a real bottleneck, and then use Instruments and the SwiftUI instrument to understand where time is being spent.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=1900s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Has Apple Intelligence within Xcode been fed with all the information and documentation around SwiftUI? Having tried to vibe code with Gemini, its ability was okay, but often hit recurring issues with Swift.</h2><p>Large language models can produce incorrect results with SwiftUI, especially around newer APIs that may not have been in their training data. This year, Apple introduced SwiftUI skills that include internal knowledge and best practices for data flow, new APIs, and building better SwiftUI experiences.</p><p>When you use agentic coding in Xcode, those skills are available out of the box and can be invoked automatically based on context. For example, if you have a SwiftUI file open and prompt the model, Xcode can use the relevant SwiftUI skill.</p><p>There is also a way to export those skills for use with third-party models. The panel pointed to the &#8220;What&#8217;s new in SwiftUI&#8221; talk, near the end, for how to do that.</p><p>The skills should make models reason better about SwiftUI, but feedback is still important. If the new Xcode skills stumble or produce bad guidance, file feedback so Apple can improve them.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=2193s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the best practices in order to make my SwiftUI views performant and not have unnecessary view updates?</h2><p><code>@Observable</code> is a strong tool because SwiftUI establishes dependencies only for properties that are actually read by a view. If one property changes and only one view reads it, only that view needs to update.</p><p>A useful mental model is to think of your SwiftUI hierarchy as a tree. If a high-level view reads too much state, it will update more often and recreate more of its child structure. If a small leaf view reads the changing value, SwiftUI can limit the update to that smaller area.</p><p>Be careful with the environment. Environment is useful for values injected high in the hierarchy and read lower down, but if the value changes frequently, it can invalidate many views. It is great for things like color scheme and other values that do not change rapidly. It is not a good place for values such as current time in milliseconds.</p><p>Break large view bodies into smaller custom views. Moving code into a computed property can improve readability, but it does not give SwiftUI a separate invalidation boundary. A custom view can. Also avoid heavy work inside <code>body</code>, such as repeatedly creating formatters or transforming arrays. Cache or move that work elsewhere.</p><p>At the same time, if you are just getting started, do not worry about all of this too much. SwiftUI gives good performance when used naturally. Learn the model, build the app, and optimize when profiling shows that you need to.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=2425s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>In a world of agentic AI, what is the effective way to leverage AI within Xcode and SwiftUI to help ensure that the student is learning?</h2><p>Use AI as a tutor, not as a replacement for understanding. A good learning workflow is to ask the agent to explain what it produced, why it chose a certain API, and what alternatives exist. You can also ask it to quiz you, break the task into steps, or review code that you wrote yourself.</p><p>The key is to stay engaged with the output. Do not only accept generated code and move on. Read it, ask follow-up questions, and compare it with documentation or tutorials. This turns AI from a code generator into an interactive learning partner.</p><p>A particularly useful pattern is to write your own first attempt and then ask the agent for a review. Ask what can be improved, which APIs you missed, and whether the code follows SwiftUI best practices. This helps you build judgment, not only code volume.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=2910s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I&#8217;m looking for a high-level resource to build a clearer mental model of SwiftUI. Is there an official one-page overview chart or flow diagram that explains how these pieces fit together and what options developers have?</h2><p>There is not just one single document to point to, but there are several good official resources. The panel mentioned introductory SwiftUI sessions, including the intro session where Jacob builds a sample app about sandwiches, the SwiftUI Essentials video, and the one-page &#8220;Getting started with SwiftUI&#8221; resource on the developer site.</p><p>The strongest recommendation was to watch the sessions. They teach a lot quickly and help build a mental model of how SwiftUI pieces fit together: views, state, data flow, layout, and composition.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=3355s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When I use AI tools to write SwiftUI code, I keep hitting the same errors. Code looks right but won&#8217;t compile or behaves unexpectedly. How can I use Xcode&#8217;s AI tools more effectively as a beginner, so I&#8217;m learning correct SwiftUI patterns instead of picking up bad habits from broken suggestions?</h2><p>Use Xcode 27 skills. The SwiftUI skills include Apple&#8217;s recommended best practices, so models can pick up better SwiftUI patterns instead of relying only on older or incomplete training data.</p><p>Let the model check its work by compiling when possible. One of Swift&#8217;s strengths is that many errors are caught at compile time, so the model can see those errors and iterate.</p><p>Also go piece by piece. AI tools often try to implement everything at once, which can produce large, fragile changes. Smaller prompts and smaller tasks usually lead to better, more encapsulated SwiftUI code and make it easier for you to understand what changed.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=3403s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s one thing announced yesterday that you think beginners will overlook or that will matter very much as they grow?</h2><p>Layout flexibility. Beginners may overlook it, but it becomes increasingly important as apps grow across iPad, macOS, visionOS, and even iPhone mirroring where iPhone apps can be resizable.</p><p>A useful habit is to think early about how much of your app can be flexible in SwiftUI. SwiftUI gives you many layout constructs, and as you get more advanced, you can even create your own custom layout when the built-in tools are not enough.</p><p>Device Hub is also a useful new tool for testing across different devices, aspect ratios, and sizes. That helps avoid building only for the one phone size you personally have.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=3474s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How to best convert a design of an app from a designer to SwiftUI? Do you recommend the Xcode coding assistant? And if so, which one?</h2><p>The suggested approach is to look at the design, understand what the designer has built, and translate it into SwiftUI using the layout primitives SwiftUI provides. There is no dedicated SwiftUI skill that automatically handles this whole conversion.</p><p>However, there are Sketch and Figma connections that can produce SwiftUI code from designs made in those tools, so those are worth checking out. The panel did not recommend a specific coding assistant for this exact task; the answer was more about understanding the design and using the right SwiftUI layout tools, with Figma and Sketch integrations as helpful starting points.</p><p><a href="https://www.youtube.com/watch?v=IbNHuOt5YHA&amp;t=3548s">&#9201;&#65039;Time code</a></p><div><hr></div><h2><strong>&#127942;</strong>Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared beginner-friendly, practical, and thoughtful questions throughout the session. Your questions made the discussion useful not only for people new to SwiftUI, but also for developers coming from web, Java, UIKit, or other platforms.</p><p>Question acknowledgments: Donald, AI did Luty, Seahan, Willpix, MKWB, Classic Flame, Charlie, Ishucho, Azie, N586 FL, Noel Lee Linger, Fossil Coder, Florentine F, Matasa E.</p><p>Finally, a heartfelt thank-you to Kurt, Sema, Gabriel, Jeff, Sam, and Trevor for leading the session, sharing practical guidance, and making SwiftUI approachable for new Apple platform developers.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[WWDC26: Swift Group Lab - Q&A]]></title><description><![CDATA[Direct answers from Apple Developers Team]]></description><link>https://antongubarenko.substack.com/p/wwdc26-swift-group-lab-q-and-a</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/wwdc26-swift-group-lab-q-and-a</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Wed, 10 Jun 2026 20:15:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9fb0c6be-c09a-4fb8-ad84-dc4a4621a112_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Any WWDC is a wonderful time! You watch tons of videos, read long articles and docs, and can even join live sessions with Apple developers to ask them questions. This format has become one of the most anticipated parts of the past year. Many of us, including me, have loved it and were glad to join.</p><p>WWDC26 gives us an opportunity not only to talk about already shipped features and best practices, but also to ask about the freshly announced ones.</p><p>I joined most of the sessions and, as usual, prepared a transcript: grouped, polished, and enriched with time codes. And there are plenty of sessions still to go! So I decided to start a series, probably a long one, with gathered Q&amp;A from those Labs.</p><p>As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.</p><blockquote><p>I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.</p></blockquote><p>Enjoy! And subscribe so you don&#8217;t miss the next Lab.</p><div><hr></div><h2>What&#8217;s the best way to transfer &#8220;ownership&#8221; of data from one isolation domain to another. For example, an actor that generates a large amount of non-<code>Sendable</code> data and then wants to pass this data off to another actor without making a copy, relinquishing its own ability to access the data.</h2><p>The Swift concurrency model has this concept called region-based isolation, where you are allowed to transfer non-<code>Sendable</code> data from one actor to another as long as the original actor can no longer access that non-<code>Sendable</code> data after that point of transfer. There are some cases where the compiler can just know that this is safe because of your use of those values.</p><p>If you want to deliberately annotate parameters or return types, there is a keyword called <code>sending</code> that indicates that it is some non-<code>Sendable</code> value that you are going to transfer off or return from an actor to be used somewhere else safely. If you need to store those values, that is something you can only accomplish with some of the unsafe opt-outs today, but there is an active pitch on the forums. Right now, I think it is called <code>disconnected</code>, and it is a data type that is meant to preserve that property through actually storing those values if you need to store it and then later transfer that somewhere else. So that is an active area of language evolution.</p><p>If you have feedback for that, definitely join the conversation in the forums because it is still being designed and discussed.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=265s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is some advice you give when using Swift structured concurrency. Some best practices / some pitfalls.</h2><p>Structured concurrency works best if you lean into it as assertively as you can. Once you start adding little escape hatches from structured concurrency, you can get into trouble. It helps to find smaller parts of your codebase that you can refactor as a single unit.</p><p>You should avoid, at almost all costs, creating unstructured tasks, such as <code>Task.detached</code> or regular task initializer unstructured tasks, unless you are trying to send some work elsewhere to be done. You should not expect to do that in the mainline flow. Task groups become your friend, and where you can, you want to make sure you have objects whose lifecycles fit those task groups with that lexical scope. Create an object, use it in that lexical scope, and stop using it again.</p><p>There are a lot of <code>with</code>-style functions in Swift. Swift has a bit of an idiom around using <code>with</code>-style functions, and this can help because it gives you a nice lexical spelling. You do not have to use that everywhere; for example, you can use <code>deinit</code>-based cleanup in places if that works for you. But you do want to focus on doing things that way.</p><p>Once you have made those initial steps, the next trick tends to be: do not fan out too much. Write linear asynchronous code in your task groups. Each task should not try to do too many things in parallel. It should be a recipe: A, then B, then C, then D. When you have things that need to happen in parallel, then you can use your task groups. Patterns like fork-join, fan-out/fan-in, or scatter-gather are useful for this kind of structured concurrency work when you have a lot of repetitive work to do.</p><p>Cancellation shields are also important. One of structured concurrency&#8217;s big wins is automated cancellation propagation. When you want to cancel the whole task, you can say that everything being done in service of this is no longer necessary. Maybe the view was dismissed, or on the server side, maybe the connection was torn down and the client went away. But you might have opened a resource or a handle to something that needs to be cleaned up, and that cleanup itself may be asynchronous.</p><p>A common example is file I/O. You might have partly written a file and need to flush the data into a known good state. Or you might have a database transaction that you want to cancel. These pieces of asynchronous cleanup can become problematic if you are not using cancellation shields, because you are executing in a cancelled context. Most Swift code will try to unwind as rapidly as possible in a cancelled context. In those cases, remembering to have your cleanup use cancellation shields is an important pattern, and it is a great companion to <code>async defer</code>.</p><p>You can look at your <code>async defer</code> blocks and ask whether anything inside will actually execute correctly. If there are <code>await</code>calls in there that you think really will suspend, maybe you should put that in a cancellation shield as well. That can be a really good one-two punch for getting resources cleaned up nicely.</p><p>Non-<code>Sendable</code> types can also help. A non-<code>Sendable</code> type cannot cross concurrency domains, and in a lot of structured concurrency you do not really have concurrency; you just have asynchronous code. Within that asynchronous control flow, you can use a non-<code>Sendable</code> type freely, but you cannot accidentally escape it out. When you embrace structured concurrency, non-<code>Sendable</code> types help you reason about the fact that something is going to be treated linearly. There is no concurrency there. So when you do go concurrent, you have a smaller set of things to think about.</p><p>People often ask how to make their data type <code>Sendable</code>, but maybe you should be making some data types non-<code>Sendable</code>. For ephemeral data types used as part of a computation, making them explicitly non-<code>Sendable</code> can help your data model fit much more cleanly. It gives you a clear delineation between objects you expect to pass around and objects you do not.</p><p>This can also help with performance, because objects you pass around should be cheap to pass around, which might not be what you need for intermediate types. In Swift 6.4, there is a nicer syntax for explicitly making a type non-<code>Sendable</code>: <code>~Sendable</code>, similar to <code>~Copyable</code>. Previously, you had to write an unavailable conformance to <code>Sendable</code>.</p><p>One more point: do not rush so far into adopting Swift 6 mode that you use <code>unchecked</code> too much. If you do that, you deprive yourself of the benefit the compiler can give you to know that the code is safe. Leaning on the compiler and using real logic to make sure something is truly <code>Sendable</code> is important.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=371s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is there an overhead cost to unused/unnecessary conformances such as <code>Sendable</code>, <code>Equatable</code>, <code>Hashable</code>, <code>Identifiable</code>, <code>Comparable</code> etc... to every <code>struct</code> just out of easier compiling habits? What does really happen under the hood when such is called upon? Please advise on best practices &amp; common pitfalls.</h2><p>There is some cost to having extra conformances to things like <code>Equatable</code> and <code>Hashable</code>. In those cases, you have the code associated with the equality function or the hashing function that is needed to support that conformance, and the conformance itself exists even if you do not use it directly in your code.</p><p>It could be found later, for example if you do an <code>as?</code> cast to an existential type like <code>any Equatable</code> or <code>any Hashable</code>. It can discover that code and that conformance at runtime, and for that reason the compiler will keep those around even if you are not using them directly in your own code.</p><p>Something like <code>Sendable</code> is different. <code>Sendable</code> is essentially just a tag that says the type is sendable. It does not have a runtime representation, so it does not have a cost anywhere in the system.</p><p>It is also possible that extra conformances or generic overloads can affect compilation times. If you have many overloads that can satisfy a particular type-checking problem, it can cause type checking to get a little slower because the compiler has to sort through which overloads make sense in the larger context and which one is the best.</p><p>From an API design point of view, it is important to make sure there is a meaningful conformance to those protocols. Do not feel like you have to conform just because the protocol is there. If something is <code>Equatable</code>, make sure it actually can be equated, and the same applies to the other protocols as well. That helps prevent mistakes later when maybe the type was not actually meant to be <code>Equatable</code>, <code>Sendable</code>, or something else.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=717s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Applying <code>@MainActor</code> often triggers a massive chain reaction of async refactoring across the codebase. What is the cleanest architectural pattern to stop this &#8220;concurrency contagion&#8221; in legacy apps without sacrificing Swift 6 safety?</h2><p>When you start to add <code>MainActor</code> to a particular type in your code, you can then only use that type from another <code>MainActor</code>context. Anywhere you use that type throughout your codebase, you have to propagate the <code>MainActor</code> annotation or make that code async so that, if the code ends up running off <code>MainActor</code>, it can switch back to <code>MainActor</code> to use the safe things.</p><p>There are two general approaches you can use to minimize the amount of propagation you have to do. If everything in your module should be on <code>MainActor</code>, you can switch on <code>MainActor</code>-by-default mode. That makes everything on <code>MainActor</code>, and if you have parts of your code that are offloading work or need to be used from off <code>MainActor</code>, those are the ones you can annotate explicitly.</p><p>Otherwise, start with the leaf types in your project and go outward from there. It might also be the case that only parts of the class you are trying to make <code>MainActor</code> actually need to use mutable state. There might be other things within that type where you can selectively mark a method as <code>nonisolated</code> if it is not touching any of the class&#8217;s mutable state. That makes it easier because not all uses of that type need to be on <code>MainActor</code>. You can be more precise about which code actually needs to be on <code>MainActor</code> and which code does not.</p><p>It is also worth looking for static variables in your code. Sometimes something was written as <code>static var</code>, and sometimes it used to be a computed property that was later changed to be stored, but the <code>var</code> is never actually mutated anywhere in the code. Take that as an opportunity to make some of your state immutable. If it is not mutated anywhere, you do not have to mark it as <code>MainActor</code>, and if it can stay <code>nonisolated</code>, it is easier because you can use it from anywhere.</p><p>Some of this is also covered in the &#8220;Migrate your app to Swift 6&#8221; session from 2024. It is a code-along video where this kind of practice is done with a sample project.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=872s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the most essential modern Swift features or official resources you recommend I adopt first to ensure high efficiency and great performance? Thank you all so much.</h2><p>First of all, it is important to profile. We have great tools in Instruments that show you exactly where your time is going. There is a flame graph view that shows you a breakdown of where everything is happening, and that is what you should start with to guide the rest of your optimization. That way, you know you are optimizing the code that is actually costing you time and memory.</p><p>There was a great WWDC talk last year about performance and Swift. It used a sample app that did image processing through a series of steps. In each step, Instruments was used to show where the slowdown was, and then modern Swift techniques were applied to make that performance overhead disappear.</p><p>There has also been a big focus on performance in Swift, especially in embedded Swift this year, with a focus on <code>Span</code>, <code>UniqueArray</code>, and related work. <code>UniqueArray</code> is a new type that I think was recently accepted into Swift, and it is also available in the Swift Collections package, which has early prototypes and more work around efficient use of data structures.</p><p>On top of the flame graph, there is also a Top Functions view. You can filter down to the functions that cost more or spend more time, and use that to do the same kind of practice, filtering the flame graph a little more easily.</p><p>One more thing worth calling out is that it is not just about which APIs you use. Do not forget the basics from computer science classes: algorithms, choosing the correct algorithms, and looking at big-O performance as well.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1042s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For teams that did the full Swift 6 strict-concurrency migration and annotated everything by hand, what&#8217;s the recommended path now that the isolation model reduces the required annotations? Should we be tearing out annotations that are now redundant, or leave them and let them become no-ops?</h2><p>It is completely fine to have annotations that are redundant in your code. Some people prefer to make things more explicit in certain cases, especially if something is inferred in a way that might not be obvious or might change with a later modification to the code.</p><p>There used to be a compiler warning when you were writing generic code and stated a conformance requirement on a type parameter that was implied by something else. Later, a bug was fixed that made that warning much more accurate, so people saw it more often. People complained that they actually liked restating that conformance requirement because it was helpful as documentation and made the function signature easier to read without mentally working through what was implied by another conformance requirement. So the warning was removed. It is fine to restate things that are redundant.</p><p>If something is evident from elsewhere in the code, you can remove the redundant annotations if that is your preference. For example, if you have <code>nonisolated</code> on a bunch of methods in an extension, you used to not be able to write <code>nonisolated</code> on the extension itself. Now you can write it directly on the extension and remove those <code>nonisolated</code> modifiers from the individual methods if you prefer. But they are not harmful.</p><p>Sometimes explicit annotations provide value. They mean someone thought about this and said that the type absolutely must be <code>Sendable</code>, or absolutely should not be <code>Sendable</code>. That is documentation. If someone later changes the type in a way that breaks it, they understand that this was deliberate and should not change that fundamental property.</p><p>That said, whenever you do that, it is often a good idea to write a small comment above it explaining why. Otherwise, you may come back later and know you meant something by it, but not remember what.</p><p>When working on very large projects, you often end up writing little helpers. It is a good idea to behave as though those helpers have a little API contract that you are going to hold yourself to, even if they are internal. Defining what you think the surface should be can help. Applying annotations that the compiler may already infer can still be extremely helpful.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1199s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Why is <code>UserDefaults</code> not <code>Sendable</code> given that the doc says it is thread-safe? Is this a preliminary step?</h2><p>A few years ago, when Swift introduced <code>Sendable</code>, Foundation did an audit of everything and looked at which things were definitely <code>Sendable</code>, which things were absolutely not <code>Sendable</code>, and then there was a third category of things that were classes. Maybe the superclass is <code>Sendable</code>, but a subclass is not.</p><p>An easy example is <code>NSString</code>: it is immutable and <code>Sendable</code>, but <code>NSMutableString</code> is a subclass and is obviously mutable and not <code>Sendable</code>. For classes where the type is generally not subclassed often but could be subclassed, Foundation had this middle state of &#8220;it is not safe.&#8221; We did not want to rush to mark something as <code>Sendable</code> if it was not actually true, because that defeats the purpose of the exercise. For some of those cases, we chose to mark them as not <code>Sendable</code>.</p><p>There is a new feature in Swift 6.4 that allows those types to be annotated more accurately. That is the <code>~Sendable</code> feature. It is not quite the same thing as an unavailable conformance to <code>Sendable</code>. An unavailable conformance says that this type and all of its subclasses are definitely not <code>Sendable</code>. With <code>~Sendable</code>, it is just a lack of conformance to <code>Sendable</code>. Subclasses could have that unavailable conformance to <code>Sendable</code> if they have mutable state, but other subclasses, if they do not add mutable state and are perfectly thread safe, can add a <code>Sendable</code> conformance. It allows more flexibility where subclasses can be either <code>Sendable</code> or not, instead of saying the whole class hierarchy is definitely not <code>Sendable</code>.</p><p>The standard global <code>UserDefaults</code> should give you a <code>Sendable</code> conformance, so that is also something to check into.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1394s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Would it be good practice to migrate to using <code>borrow</code>/<code>mutate</code> instead of <code>get</code>/<code>set </code>altogether? In what cases would you not recommend migrating to?</h2><p><code>borrow</code> and <code>mutate</code> are part of the ownership model that has been fleshed out in Swift. <code>borrow</code> says that you are essentially getting a reference to some data that is held elsewhere, for example in the struct where the property being borrowed lives. <code>mutate</code> is like a reference, but a mutable reference, so it can be used when you want to change that data.</p><p>That is different from <code>get</code> and <code>set</code>. <code>get</code> produces a new value. It could be referencing a value inside your struct, or it could have been computed on the fly. Similarly, <code>set</code> can go through any amount of code to make that change, because you are updating at that point.</p><p><code>borrow</code> and <code>mutate</code> can be more efficient because you do not have to create copies and you are not running extra code. You are just referencing something that is already there. However, it only works when there is something there. In the case of <code>mutate</code>, when you want to mutate something, you have to be the only person that can refer to that data, and the compiler will make sure you are the only person modifying that data. So it is a more restrictive model to get better performance.</p><p>Where you are performance-sensitive and essentially sharing out data that you are already storing, <code>borrow</code> and <code>mutate</code> are better. For all the other cases, use <code>get</code> and <code>set</code>.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1530s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>With a lot of additions like <code>Mutex</code>, <code>InlineArray</code>, <code>Span</code>, typed throws, non-copyable types etc. How do we as app developers know what&#8217;s meant for us versus systems or embedded developers? And how do you recommend keeping up with the pace? Many developers find them overwhelming.</h2><p>Swift is designed around progressive disclosure. You should not have to learn all of the features if you are just getting started. The people working on these features have been trying to keep that in mind.</p><p>Going back to performance, it does not make sense to jump into using non-copyable types if they do not make sense for your app. You should measure first and see whether there is an issue. Nate&#8217;s talk from last year talks about non-copyable types and describes the patterns you will see in Instruments that indicate you may need a non-copyable type. I would not start with that, because these features do add complexity that you may not need from the start.</p><p>That said, these features are meant to work well together. Unique types, spans, and sendability analysis should complement each other. When you are in that space, they should work together well so you do not feel like you are mixing and matching unrelated tools.</p><p>The intent is that when you encounter a problem, such as a performance problem, there is a tool that can help you. The tool should be similar to what you have already been using, but with greater restrictions that allow Swift to compile faster code or use less memory.</p><p>For example, <code>UniqueArray</code> is similar to <code>Array</code>. We use arrays everywhere, and they have copy-on-write semantics, which are great for general optimization and easy to use. If you end up with a performance problem where you see extra copies or retain/release traffic in Instruments, then you can replace your array with a <code>UniqueArray</code>. That will take some work. The compiler will tell you where you are trying to modify it in two places or share it in places where you cannot. But then the compiler guides you into tighter restrictions around the type that allow it to perform better at runtime.</p><p>You should not feel the need to learn every Swift feature. Instead, look for where you are having trouble, and there will be a tool in Swift to help that part of your code improve performance or gain extra expressivity. That is the time to learn the tool.</p><p>Language features are not collectibles. You do not get a prize for having one of each. You have a limited number of hours in the day and things you need to get done. It is easy to spend too much time trying to make advanced performance features work in a setting where they were never needed. If you have already achieved the performance you need, your time is probably better spent on bug fixes or new features rather than squeezing every nanosecond out of the system.</p><p>Sometimes that really is necessary, and usually you will know those moments when you see them. You will spot them in profiling and realize that is where you need to go. Those areas tend to be isolated, too. When you adopt these features, do not decide that your whole project has to convert to <code>UniqueArray</code>. Keep them in areas that are on their own so you do not create a giant refactoring project. Introducing spans or <code>UniqueArray</code> along the hottest path can give you almost all of the performance gains for a fairly small code change supported by the compiler.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1635s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Our project has very slow incremental builds, with Swift Emit Module often taking minutes. Splitting into smaller modules didn&#8217;t help much. Can Swift features like type inference, generics, associated types affect incremental build performance? How would you diagnose the root cause?</h2><p>These features can affect build performance in the extreme, but generally they do not affect a project&#8217;s build performance overall. Once in a while, you will hit a particular expression that takes a long time.</p><p>If it is specifically the module emission phase that is slow, I would expect it to be more related to all the other modules being imported. With explicit module builds, which have been rolling out in Xcode and Swift over the last couple of years, and with the build timeline in Xcode, you can start looking at where the compiler is actually spending its time.</p><p>You may have excess dependencies that are causing rebuilds or large rebuilds, and those may be easier to prune when looking at that data. In a sense, it is like performance-tuning your code: you are performance-tuning your build. You have to go and see what is actually happening to find the places to optimize.</p><p>If you want to learn more about explicit module builds, there is a session from a couple of years ago that explores that topic. Explicit module builds are on by default now.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=1921s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Now that Swift 6 concurrency has been out long enough to see how developers actually adopted it, is there anything the team would approach differently if designing it today?</h2><p>Yes. There are two changes throughout the evolution of Swift concurrency where, if we could go back to the very start, we would probably just make the latest change we made. That is the behavior of async functions, particularly where a <code>nonisolated async</code> function actually runs.</p><p>There were two different proposals that made changes to the behavior of <code>nonisolated async</code> functions. One made them always switch to the global concurrent thread pool to run there. A later proposal in Swift 6.2 made them stay on whatever context they were called from. So if you called it from <code>MainActor</code>, the function would stay there.</p><p>This was based on real-world experience and feedback from people using async functions in real-world code. People who had adopted Swift&#8217;s early concurrency warnings, later complete concurrency checking, in apps, services, and libraries found that they were passing a lot of non-<code>Sendable</code> types back and forth between an actor-isolated context and these <code>nonisolated async</code> functions. That caused a lot of data-safety errors because the original actor-isolated context would still have access to those non-<code>Sendable</code> values at the same time that the async function was running on the global concurrent thread pool.</p><p>A much better default was to keep them running in the context where they are called from. That means there is no issue with possible concurrent access to those non-<code>Sendable</code> values. The behavior where you offload to the global concurrent thread pool is still useful, but that should be explicit and opt-in.</p><p>I held on for a long time to the belief that those functions running on the global concurrent thread pool was the right long-term model, but I was convinced over time by the issues that caused in real-world projects. That insight would not have been possible without real-world adoption. If we could go back, it would be nice to have had that insight from the beginning and not require a transition where people adjust annotations to adopt the new behavior.</p><p>It is an interesting trade-off. From a whole-systems perspective, having more concurrency available lets you get more parallelism out of your system and can be better for performance. But once people tried using the full safety model, it pushed many more types toward being <code>Sendable</code>, and that was not the natural way to express all of these ideas. The newer model is more approachable, more like how things work when they are not concurrent, and more explicit about the points where you introduce concurrency to unlock parallelism.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=2019s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are the notable Swift Package Manager improvements in the latest Xcode/Swift release - especially around build and dependency-resolution performance? Are there changes I should adopt to speed up builds in a large multi-package project?</h2><p>The biggest change for Swift Package Manager in the Swift 6.4 release is one you might not notice because it is not something you have to opt into. Previously, Swift Package Manager in Xcode and Swift Package Manager used in other IDEs, like VS Code with the open-source <a href="http://swift.org/">swift.org</a> toolchains, used different build system implementations. Now both are unified using the Swift Build package.</p><p>That brings much more consistency between the two build systems. There is a single point of maintenance for bug fixes and future improvements, and you will see those improvements in Xcode and anywhere else you use Swift Package Manager. There was a preview of this in Swift 6.3, and in Swift 6.4 it is on by default. So it is not something you need to opt into; it is more of an under-the-hood infrastructure improvement.</p><p>There are several performance optimizations that were in the Swift Build system and now come to normal package builds because of that. Explicit modules are one example. Swift Build can also be better about breaking apart the build of a module into separate pieces, so you get more parallelism out of your build from this unification.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=2256s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the one Swift feature most developers don&#8217;t know exists but should?</h2><p>One useful combination is <code>@inlinable</code> with <code>@inline(never)</code>. <code>@inlinable</code> is incredibly powerful across module boundaries. It unlocks many optimization opportunities, not only inlining. It can also unlock things like generic specialization or effects propagation that help the optimizer understand exactly what is going on in your code.</p><p>You can combine <code>@inlinable</code> with <code>@inline(never)</code>, which says that under no circumstances should this function be inlined. That turns out to be a useful performance tool for generic code with cold paths. For example, if you have copy functions that fall back to a laborious and slow byte-wise copy operation, it can be helpful to tell the compiler not to inline that part. You want the fast path inlined because it is hit all the time, but not the slow path that would generate a lot of code. It is a powerful trick for unusual performance problems where you cannot get the compiler to reliably inline the thing you want.</p><p>A simpler one is writing type annotations with <code>as</code>. If you have written a large expression and got an ambiguity error from the compiler because there are many overloads, and you know what a specific type should be, you can write a type annotation in that part of the expression to influence overload resolution. Sometimes that guides overload resolution correctly, sometimes it causes a type parameter to be inferred with a concrete type that was not otherwise available, and sometimes it simply gives you a more precise error message because you were more explicit.</p><p>Another important &#8220;feature&#8221; is that Swift is an open-source project. The open-source part of Swift is not only for non-Darwin platforms. The Swift language, standard library, Foundation, networking APIs, and other APIs discussed here are developed and discussed on the forums. Feedback from iOS developers is valuable, even if they never think about Swift on the server. You do not have to contribute a pull request to participate. Asking questions about compiler diagnostics, explaining what confused you, or saying that an API proposal looks good are all valuable forms of feedback.</p><p>There are also integer overflow APIs. They can be easy to miss, but if you are working with integers that may get large quickly, Swift has APIs that let you carry the overflow flag around so you can run a straight-line computation and then check at the end whether anything went wrong.</p><p>Key paths are another feature that people sometimes forget about. They let you refer to a property in the abstract without an instance, which can avoid building large piles of closures when you need to abstract over different properties.</p><p>Finally, Swiftly is worth checking out if you want to install and try newer toolchains, including experimental features. It is familiar if you use VSCode or the CLI, but you can also use it from Xcode.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=2365s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s left in language evolution to get tuples to finally conform to <code>Equatable</code>, <code>Hashable</code>, <code>Comparable</code>, etc. conditionally?</h2><p>This is an evolution of parameter packs. We now have the base concept in the language. What is needed to support conditional conformances of tuples to <code>Equatable</code>, <code>Hashable</code>, and similar protocols is the ability to write an extension over a tuple type where its element types are represented with a parameter pack, because each element could have a different concrete type.</p><p>It does not matter what those concrete types are. We just need the ability to write a <code>where</code> clause with the condition that each element in the parameter pack conforms to the protocol you are looking to conform to. If you are familiar with parameter packs, the keyword is literally <code>each</code>: something like saying where each <code>T</code> conforms to the protocol.</p><p>So we need syntax to write a tuple with a parameter pack, and similarly, extension syntax to write a parameterized extension where the extension itself has a parameter pack. That is basically it. It is a fairly small evolution to parameter packs.</p><p>There is an experimental implementation in the compiler repository right now. It is not completely there yet, but it is almost all the way there. Once that is done, the older proposal can be revisited. The older proposal used more bespoke conformances for those three protocols specifically, but now that we have parameter packs, the goal is to offer this as a general feature. That way, you could add conformances to your own protocols for tuples where that makes sense as well.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=2897s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>With Swift 6 strict concurrency enforcements, what is the recommended pattern to ingest high-frequency sensor data on a background Actor and safely stream those updates to an <code>@Observable</code> data model on the <code>MainActor </code>without blocking the UI?</h2><p>The question depends on what &#8220;high frequency&#8221; means in this context, and exactly how high that frequency is. There are different domains where the answer will change. If high frequency is still substantially lower than the rest of your UI updates, you might have no work to do. It may be straightforward.</p><p>Assuming it really is high frequency, one of the things you want to do is avoid context switching excessively. You want to make the switches from your background actor to your observable data model and UI as infrequent as possible.</p><p>There are a couple of paths here. One is debouncing. Another is accumulating data. Rather than sending one update for every data change, can you coalesce those changes into a smaller number of events and bring them over together?</p><p>There is also a question about data loss. How much data loss is acceptable for your background sensor? For example, if you are measuring electrical voltage, do you need an update every nanosecond rendered to the screen? Probably not. You can likely debounce with data loss, and it is fine if you do not print every intermediate value.</p><p>You can also save the data elsewhere while the UI only shows what it actually needs to show. SwiftUI and <code>@Observable </code>naturally coalesce updates for efficiency, which you get for free. Consider the requirements of what the UI really has to show.</p><p>If there is a lot of work happening, it is important to make sure that work is happening asynchronously in the first place, and to keep the UI updates only for what the user needs to see. That might be a different problem from having to stream everything, so it may help to split the problem in half.</p><p>If you need to do debouncing, Swift Async Algorithms has a debounce implementation that you can fit on top of a standard async sequence model, which is a good way to start.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=3013s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Performance question: Why tuple is more expensive than struct when returned from a method? 5-10 msec vs 1-3 msec. Or it&#8217;s a custom case, and no difference: call stack size, access to tuple items/struct fields etc.?</h2><p>That was surprising to the panel, but tuples and structs are handled differently. This gets down into the guts of the compiler.</p><p>Generally, when you are dealing with structs, the whole struct is passed as a single entity. When you have a tuple, the compiler will often break it apart. If you are passing a tuple into a function, it can be as if you had sent each element of the tuple as a separate parameter. Inside the compiler, this is called exploding the tuple.</p><p>It is plausible that if you had a very large tuple, this would be less performant than keeping everything together as you might do with a large structure. Maybe that is what is happening here, but the team would need to look at the actual code and what the optimizer produces to understand why.</p><p>The recommendation was to file a GitHub issue with a sample project.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=3181s">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s your favorite quality of live / quality of code feature in Swift? Not the obvious ones like structured concurrency, but neat, lesser known things that make Swift especially fun to write.</h2><p>One forward-looking answer is the new set of protocols called <code>Iterable</code>. This ties in with non-copyable and non-escapable performance work. The interesting part is taking what might be a complex topic and making it feel like natural Swift. For example, you want to use <code>for-in</code> over a <code>Span</code>. That sounds like a simple problem statement, but implementing it in a way that preserves safety, lifetime safety, and memory safety becomes a challenging design problem.</p><p>This is an area to watch. It is expected to help build up new container types in Swift and new ways to think about how data is structured, while preserving progressive disclosure and keeping Swift easy to use from the start.</p><p>Another favorite quality-of-life feature is the general ability in Swift to build nice APIs that feel natural while still preserving strong guarantees. Some of the features mentioned earlier, such as key paths, integer overflow APIs, type annotations with <code>as</code>, Swift Testing, and the open Swift Evolution process, also fit into that category of smaller things that can make Swift especially fun and effective to write.</p><p><a href="https://www.youtube.com/watch?v=DnMNTWlWzOY&amp;t=3262s">&#9201;&#65039;Time code</a></p><div><hr></div><h2><strong>&#127942;</strong>Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared thoughtful, insightful, and engaging questions throughout the session. Your curiosity and input made the discussion truly rich and collaborative.</p><p>Question acknowledgments: siracusa, ashrafi, sjk_27, CaiqueMP, YingZHU, mtraversonyy, petarbelokonski, paraipan, Jason-Chung, florentinf, mirkokg, pyrtsa, charly_polley, seriyvolk83, Yoorbo.</p><p>Finally, a heartfelt thank-you to the Apple employees for leading the session, sharing expert guidance, and offering clear explanations of Swift and concurrency topics. Your contributions made this an outstanding learning experience for everyone involved.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Swift Bits: Xcode Archive Migration]]></title><description><![CDATA[Free disc space and keep Xcode alive]]></description><link>https://antongubarenko.substack.com/p/swift-bits-xcode-archive-migration</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-xcode-archive-migration</guid><pubDate>Mon, 08 Jun 2026 07:19:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/50228c4b-cc76-4082-aed5-3d7b49da8ff7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It&#8217;s WWDC week &#127881;! Prepare your MacBooks, Apple Developer apps, Apple TVs, and get ready for the latest betas.</p><p>For me, it always leads to a disk space audit and cleanup on some devices. Projects and experiments are fun and interesting until you have 8 GB left and SPM refuses to load &#128556;.</p><p>And for teams without CI/CD, where many builds are shared through TestFlight, one of the biggest storage hogs is Xcode Archives. I&#8217;m sharing how you can move them to a separate location and free up some valuable space.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xUnS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xUnS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 424w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 848w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 1272w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xUnS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png" width="648" height="828.36" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1534,&quot;width&quot;:1200,&quot;resizeWidth&quot;:648,&quot;bytes&quot;:142729,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/201108201?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xUnS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 424w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 848w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 1272w, https://substackcdn.com/image/fetch/$s_!xUnS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51f38fd9-492e-4022-88d7-a132c00b805c_1200x1534.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Swift Bits: Broken SPM Adding]]></title><description><![CDATA[Restore SPM adding feature]]></description><link>https://antongubarenko.substack.com/p/swift-bits-broken-spm-adding</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-broken-spm-adding</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Tue, 02 Jun 2026 08:53:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8e25957b-ef50-4687-913a-5b009af0f007_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last weekend, a QA session hit me hard. First of all, it was a weekend. Secondly, the <code>TextField</code> refused to behave the way I wanted with the keyboard &#8212; and more importantly, the way it was described in the specs.</p><p>The struggle between spending more time fixing it or rebuilding the whole view using plain UIKit kept growing. Before taking the final step into the controlled world of <code>UITextField</code>, there was one controversial option left: using introspection to access the underlying field from SwiftUI.</p><p>For that, I used one of my favorite libraries &#8212; <a href="https://github.com/siteline/swiftui-introspect">Introspect</a>. But that&#8217;s where the story behind this post begins. SPM decided to cause some trouble as well&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ium5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ium5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 424w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 848w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 1272w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ium5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png" width="1456" height="807" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:807,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:133509,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/200177454?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ium5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 424w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 848w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 1272w, https://substackcdn.com/image/fetch/$s_!Ium5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69da5ca7-7417-44f7-9d2a-be26b308b59b_2161x1198.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Infinite loader</figcaption></figure></div><p>Swift Package Manager itself may be fine. Your package URL may be fine. Your repo may even clone normally in Terminal. And still, Xcode&#8217;s <strong>Add Package Dependency</strong> sheet can sit there forever, spinning without validating anything.</p><p>That is what makes this bug so annoying: it looks like a networking or SPM problem, but the workaround that helps most often points somewhere else entirely &#8212; Xcode&#8217;s own remembered package-entry state.</p><div><hr></div><h2>The Symptom</h2><p>The original report is very specific: Xcode&#8217;s Swift Package sheet loads indefinitely whether a URL is entered or not, and clearing DerivedData plus SwiftPM caches does not help. The issue also affects multiple projects, not just one workspace.</p><p>That combination is a useful clue.</p><p>If the problem survives:</p><ul><li><p>multiple projects</p></li><li><p>empty or filled package URLs</p></li><li><p>DerivedData cleanup</p></li><li><p>SwiftPM cache cleanup</p></li></ul><p>then the bug is probably not inside your package manifest or repository alone. It starts looking more like Xcode UI state or package-assistant state.</p><div><hr></div><h2>The First Workaround: Clear Package History</h2><ol><li><p>Open the stuck Add Package screen</p></li><li><p>Right Click on Recently Used</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dZrQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dZrQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 424w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 848w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 1272w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dZrQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png" width="592" height="132.53731343283582" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:270,&quot;width&quot;:1206,&quot;resizeWidth&quot;:592,&quot;bytes&quot;:115319,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/200177454?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dZrQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 424w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 848w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 1272w, https://substackcdn.com/image/fetch/$s_!dZrQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F714dae08-cfff-4e82-8465-bbc694aa880b_1206x270.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div></li><li><p>Clear Recently Used&#8230;</p></li><li><p>Try adding the package again</p></li></ol><p>That is already pretty revealing. If clearing history fixes the infinite loader, the issue is not just &#8220;SPM cannot resolve packages.&#8221; It suggests the Add Package assistant may be choking on recently used package metadata or state associated with that UI.</p><p>This is also why the bug feels so strange: the visible failure is &#8220;loading forever,&#8221; but the successful fix is effectively &#8220;wipe the remembered package list.&#8221;</p><div><hr></div><h2>The 2nd Workaround: Delete the Preference Key Manually</h2><p>Here is a more direct workaround:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;7c23c66a-4d12-42f7-8295-5d3ebe0ef600&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">plutil -remove IDESwiftPackageAdditionAssistantRecentlyUsedPackages ~/Library/Preferences/com.apple.dt.Xcode.plist
</code></pre></div><p>I recommend to fully quit the Xcode first, running the command in Terminal, then reopening Xcode and trying again.</p><p>The key name is fairly descriptive on its own. It strongly suggests that Xcode stores recently used package references for the package-addition assistant in its preferences plist. Since removing that key can restore the Add Package UI, a reasonable inference is that corrupted or problematic remembered package state can block the sheet before actual validation completes. That is an inference from the key name plus the observed workaround, not an official Apple explanation.</p><div><hr></div><h2>Why Deleting DerivedData Often Does Nothing</h2><p>This is the important distinction.</p><p>DerivedData and SwiftPM caches are usually where developers look first, and for good reason. They help with build artifacts, resolved package state, and repository caches. But, removing both <code>~/Library/Developer/Xcode/DerivedData</code> and <code>~/Library/Caches/org.swift.swiftpm/</code> did not fix the problem.</p><p>That lines up with the workaround above. If the issue is in the <strong>Add Package assistant&#8217;s preferences state</strong>, clearing build caches is simply attacking the wrong layer.</p><p>In other words:</p><ul><li><p><strong>DerivedData</strong> fixes build and indexing weirdness</p></li><li><p><strong>SwiftPM caches</strong> fix package cache weirdness</p></li><li><p><strong>this bug</strong> may live in Xcode&#8217;s package-addition UI memory instead</p></li></ul><p>That separation is the whole story.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Swift Bits: What is ADR?]]></title><description><![CDATA[Architecture Decision Record explained]]></description><link>https://antongubarenko.substack.com/p/swift-bits-what-is-adr</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-what-is-adr</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Mon, 01 Jun 2026 08:40:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/63805cb4-a8e5-4d63-8ee7-6fb3f1901f24_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There&#8217;s no clear border after which you can say: &#8220;Ohhh, now I know everything.&#8221; After that, you could switch industries &#8212; but I&#8217;m pretty sure farming also keeps getting new harvesters and growth techniques. Companies always need to sell something.</p><p>In IT, you collect new topics and terms throughout your entire career. For me, last month&#8217;s discovery was ADR.</p><p>I was reading the wonderful book &#8220;<a href="https://www.packtpub.com/en-sg/product/ai-driven-swift-architecture-9781835886540/chapter/ai-powered-code-architecture-and-legacy-system-modernization-6/section/dependency-injection-as-an-architectural-principle-ch06lvl1sec48?srsltid=AfmBOopvDgSNfRwTFcUMY_8vTxxI2WGw3mxT8ZTj6ZtTjoQRi_vCWOXq">AI Driven Swift Architecture</a>&#8221; by Walid Sassi and Dave Poirier &#8212; highly recommended. You can find it on Amazon or directly on the Packt website.</p><p>One chapter explained how to make LLM output more deterministic, and one of the approaches mentioned there was ADR. Sharing the details with you.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RXSZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RXSZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 424w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 848w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 1272w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RXSZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png" width="1200" height="1904" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1904,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188653,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/200090383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RXSZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 424w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 848w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 1272w, https://substackcdn.com/image/fetch/$s_!RXSZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2c12576-f32c-47d9-a79f-ad41ec1976ba_1200x1904.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Useful links:</p><ul><li><p><a href="https://adr.github.io">What is ADR?</a></p></li><li><p><a href="https://medium.com/@naveenatmakur/what-is-adr-do-i-need-it-how-is-it-useful-b5f9802db56f">What is Architecture Decision Record (ADR)? Do I need it?</a></p></li><li><p><a href="https://github.com/architecture-decision-record/architecture-decision-record">ADR Examples</a><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/displayscale"><br></a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Swift Bits: MCP Advantages]]></title><description><![CDATA[Get your AI agent new pair of tools]]></description><link>https://antongubarenko.substack.com/p/swift-bits-mcp-advantages</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-mcp-advantages</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Mon, 25 May 2026 08:13:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6005b2ac-6ac7-4fac-98e2-0c608f21e35e_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many developers struggle to get stable results from AI tool interactions. This can even build a strong opinion that such tools are unreliable: &#8220;We don&#8217;t need them. I&#8217;m used to doing everything on my own.&#8221; Fair enough.</p><p>But just like Xcode, the Simulator, or any other development tool/service, AI tools need to be tuned and adapted. Even cars have seat, steering wheel, and mirror adjustments.</p><p>Pasting a Figma screenshot or exported image is not the same as letting Claude (for example) traverse the structure itself, inspect groups, understand paddings, and consume tokens for actual context. It&#8217;s like drawing from a photo versus drawing from a live object. Yes, AI can estimate corner radii or pick colors from an image. But fonts, spacing, hierarchy? That gets complicated.</p><p>And I&#8217;m only talking about Figma right now. There&#8217;s also Notion, Slack, Google Docs, Linear&#8230; the list is huge.</p><p>Give your CLI a pair of glasses &#8212; or a few extra hands &#8212; to make its understanding more precise.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ediB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ediB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 424w, https://substackcdn.com/image/fetch/$s_!ediB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 848w, https://substackcdn.com/image/fetch/$s_!ediB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 1272w, https://substackcdn.com/image/fetch/$s_!ediB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ediB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png" width="1202" height="1858" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1858,&quot;width&quot;:1202,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:236504,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/199159746?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ediB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 424w, https://substackcdn.com/image/fetch/$s_!ediB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 848w, https://substackcdn.com/image/fetch/$s_!ediB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 1272w, https://substackcdn.com/image/fetch/$s_!ediB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b74ad85-08e3-49c5-b899-42944c465c02_1202x1858.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Useful links:</p><ul><li><p><a href="https://modelcontextprotocol.io/docs/getting-started/intro">What is MPC?</a></p></li><li><p><a href="https://code.claude.com/docs/en/mcp">Connect MCP in Claude</a></p></li><li><p><a href="https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server">Connect Figma MCP</a></p></li><li><p><a href="https://developers.notion.com/guides/mcp/overview">Connect Notion MCP</a></p></li><li><p><a href="https://slack.com/intl/en-gb/help/articles/48855576908307-Guide-to-the-Slack-MCP-server">Connect Slack MCP</a><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/displayscale"><br></a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Swift Bits: Display Scale]]></title><description><![CDATA[Screen data the right way]]></description><link>https://antongubarenko.substack.com/p/swift-bits-display-scale</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-display-scale</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Tue, 12 May 2026 07:35:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8p4w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While actively coding, it&#8217;s typical to face warnings and try to resolve them. It&#8217;s not just a fancy habit, but a way to reduce tech debt and extra compilation time. Who isn&#8217;t dreaming about zero(0, totally no) warnings during compilation? Honestly?!<br><br>Over the years, Apple has been shifting toward a multi-scene architecture. We saw the migration away from AppDelegate in SwiftUI, which finally led to using the right tools for screen info tracking. One of the most vital pieces of data is display scale. Have you ever seen an image with really low resolution quality on a 3x scale device? That&#8217;s exactly what happens when scale isn&#8217;t handled correctly.<br><br>Sharing new ways to get it in both UIKit and SwiftUI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8p4w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8p4w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 424w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 848w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 1272w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8p4w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png" width="1456" height="2878" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2878,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:263343,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/197319029?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8p4w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 424w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 848w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 1272w, https://substackcdn.com/image/fetch/$s_!8p4w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb281f335-68ed-4cea-a79d-c5209d40b8ad_1803x3564.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Useful links:</p><ul><li><p><a href="https://developer.apple.com/documentation/uikit/uiscreen/main">UIScreen.main</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uitraitcollection/current">UITraitCollection.current</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiwindowscene">UIWindowScene</a></p></li><li><p><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/displayscale">SwiftUI: displayScale<br></a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Swift Bits: Scene for Hosted SwiftUI View]]></title><description><![CDATA[Track Phases correctly]]></description><link>https://antongubarenko.substack.com/p/swift-bits-scene-for-hosted-swiftui</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-scene-for-hosted-swiftui</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Mon, 11 May 2026 06:45:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/42e66875-d7a4-4a92-af1c-b334e699da19_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!osWi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!osWi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 424w, https://substackcdn.com/image/fetch/$s_!osWi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 848w, https://substackcdn.com/image/fetch/$s_!osWi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 1272w, https://substackcdn.com/image/fetch/$s_!osWi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!osWi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png" width="946" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:608,&quot;width&quot;:946,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:784476,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161428?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!osWi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 424w, https://substackcdn.com/image/fetch/$s_!osWi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 848w, https://substackcdn.com/image/fetch/$s_!osWi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 1272w, https://substackcdn.com/image/fetch/$s_!osWi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F672a5094-d423-4d75-8bf4-c34e8dfbbb19_946x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>scenePhase</code> is SwiftUI&#8217;s built-in way to observe scene lifecycle. Apple documents <code>ScenePhase</code> as the operational state of a scene, with values such as <code>.active</code>, <code>.inactive</code>, and <code>.background</code>. Apple also notes that the meaning of the <code>scenePhase</code> environment value depends on where you read it.</p><p>That detail matters more than it first appears. Especially, when you have set the architecture once and just adding View by View.</p><p>In a pure SwiftUI app lifecycle, <code>@Environment(\.scenePhase)</code> is the natural tool. But once a SwiftUI view is embedded inside a UIKit app through <code>UIHostingController</code>, the lifecycle owner is no longer fully SwiftUI-driven. In that setup, <code>scenePhase</code> can be unreliable or simply not behave the way you expect from a SwiftUI <code>App</code> + <code>Scene</code> structure. Apple&#8217;s migration guidance frames scene monitoring as part of moving to the SwiftUI life cycle, which is a strong hint that this API is most at home there.</p><blockquote><p>So the short version is: <code>scenePhase</code> is native to SwiftUI scenes. If your SwiftUI view is hosted inside UIKit, lifecycle tracking is usually more reliable through UIKit notifications.</p></blockquote><div><hr></div><h2>What is <code>scenePhase </code>in SwiftUI?</h2><p>In SwiftUI, <code>scenePhase</code> tells you whether the current scene is active, inactive, or in the background. Apple&#8217;s docs define <code>ScenePhase</code> around scene state, not generic app state. That distinction is important because scenes and apps are related, but not identical, especially on platforms and configurations that support multiple scenes.</p><p>A standard SwiftUI example looks like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;e3dff906-cbf5-4edb-aa27-37bc7900622e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">import SwiftUI

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase

    var body: some View {
        Text("Hello")
            .onChange(of: scenePhase) { _, newPhase in
                switch newPhase {
                case .active:
                    print("active")
                case .inactive:
                    print("inactive")
                case .background:
                    print("background")
                @unknown default:
                    break
                }
            }
    }
}</code></pre></div><p>That works well when the app is using SwiftUI&#8217;s own lifecycle model.</p><div><hr></div><h2>Why It Can Fail Inside <code>UIHostingController?</code></h2><p>A hosted SwiftUI view still renders like SwiftUI, but it does not automatically mean it participates in the same scene lifecycle model as a SwiftUI app root.</p><p>That is the real trap. No jokes, from Star Wars.</p><p>If the app is UIKit-first, with lifecycle driven by <code>UIApplicationDelegate</code>, <code>UISceneDelegate</code>, and UIKit-managed windows, then the hosted SwiftUI subtree may not receive <code>scenePhase</code> updates in the same way a real SwiftUI scene does. Apple&#8217;s documentation does not say that every hosted SwiftUI view will always get full scene lifecycle semantics; instead, it consistently describes <code>scenePhase</code> in terms of scenes and the SwiftUI lifecycle.</p><p>That is why this bug feels confusing. The code compiles, the environment value exists, and the same view may behave differently when moved into a pure SwiftUI app.</p><div><hr></div><h2>What Actually Works Instead?</h2><p>For a UIKit-hosted SwiftUI screen, the practical answer is to observe UIKit lifecycle directly from the view.</p><p>There are two good approaches:</p><ul><li><p>app-wide notifications from <code>UIApplication</code></p></li><li><p>scene-level notifications from <code>UIScene</code></p></li></ul><p>Apple documents <code>UIApplication.didBecomeActiveNotification</code> as a notification posted when the app becomes active, and <code>UIApplication.willEnterForegroundNotification</code> as one posted shortly before the app leaves background on the way to active. Apple also documents <code>UIScene.didActivateNotification</code> and related scene notifications for scene lifecycle transitions.</p><blockquote><p>So <code>UIApplication</code> notifications are not the only solution. They are just the broadest one.</p></blockquote><h2>1.  App-wide lifecycle with <code>UIApplication </code>notifications</h2><p>If you only care whether the whole app became active or inactive, observing <code>UIApplication</code> notifications directly inside the SwiftUI view is often enough. Apple documents <code>didBecomeActiveNotification</code> and <code>willResignActiveNotification</code> specifically for those app state transitions.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;11639f2a-94cb-4187-9f33-5a6f401986d5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">import SwiftUI
import UIKit

struct HostedSwiftUIView: View {
    @State private var isAppActive = true

    var body: some View {
        //Strange way to change Text, but still
        Text(isAppActive ? "App Active" : "App Inactive")
            .onReceive(NotificationCenter.default.publisher(
                for: UIApplication.didBecomeActiveNotification
            )) { _ in
                isAppActive = true
                print("App became active")
            }
            .onReceive(NotificationCenter.default.publisher(
                for: UIApplication.willResignActiveNotification
            )) { _ in
                isAppActive = false
                print("App resigned active")
            }
    }
}</code></pre></div><h2>2.  Scene-level lifecycle with <code>UIScene </code>notifications</h2><p>If your app uses scenes and you want behavior closer to what <code>scenePhase</code> is meant to represent, <code>UIScene</code> notifications are a better match than <code>UIApplication</code> notifications.</p><p>Apple documents scene notifications including:</p><ul><li><p><code>UIScene.didActivateNotification</code></p></li><li><p><code>UIScene.willDeactivateNotification</code></p></li><li><p><code>UIScene.didEnterBackgroundNotification</code></p></li><li><p><code>UIScene.willEnterForegroundNotification</code></p></li></ul><p>That makes this pattern more scene-accurate:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;46351c9b-bce6-40ad-8c57-11d73e6ec067&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">import SwiftUI
import UIKit

struct HostedSwiftUIView: View {
    @State private var phase: ScenePhase = .active

    var body: some View {
        Text(String(describing: phase))
            .onReceive(NotificationCenter.default.publisher(
                for: UIScene.didActivateNotification
            )) { _ in
                phase = .active
                print("Scene active")
            }
            .onReceive(NotificationCenter.default.publisher(
                for: UIScene.willDeactivateNotification
            )) { _ in
                phase = .inactive
                print("Scene inactive")
            }
            .onReceive(NotificationCenter.default.publisher(
                for: UIScene.didEnterBackgroundNotification
            )) { _ in
                phase = .background
                print("Scene background")
            }
            .onReceive(NotificationCenter.default.publisher(
                for: UIScene.willEnterForegroundNotification
            )) { _ in
                phase = .inactive
                print("Scene foreground")
            }
    }
}</code></pre></div><p>For many UIKit-hosted SwiftUI screens, this is the closest practical replacement for <code>scenePhase</code>.</p><div><hr></div><h2>Is There Any Other Solution?</h2><p>There are a couple of alternatives, but they are not really better for this case.</p><p>One option is to inspect <code>windowScene.activationState</code> from UIKit and derive a current value manually. That can work for one-time reads, but by itself it does not provide observation. Another option is to move the feature into a real SwiftUI <code>App</code> / <code>Scene</code> lifecycle, where <code>scenePhase</code> is naturally supported. Apple&#8217;s migration docs make clear that lifecycle monitoring is part of the SwiftUI lifecycle model, so that route is valid, but it is usually a much larger architectural move than teams want for one hosted screen.</p><p>So for a UIKit-heavy app, the practical answer remains: bridge lifecycle from UIKit.</p><div><hr></div><h2>Recommended Rule of Thumb</h2><p>A clean rule is:</p><ul><li><p>Use <code>@Environment(\.scenePhase)</code> in a real SwiftUI <code>App</code> / <code>Scene</code></p></li><li><p>Use <code>UIApplication</code> notifications in hosted SwiftUI when app-wide state is enough</p></li><li><p>Use <code>UIScene</code> notifications in hosted SwiftUI when you need scene-level behavior</p></li></ul><p>That is more precise than saying only <code>UIApplication</code> notifications work.</p><p>They work, but they are not always the best fit.</p><div><hr></div><h2>Why This Matters?</h2><p>The bug is subtle because nothing looks obviously broken. A hosted SwiftUI view still feels like SwiftUI, so it is natural to expect <code>scenePhase</code> to behave the same everywhere.</p><p>But lifecycle ownership is one of those invisible boundaries that changes behavior dramatically. <code>scenePhase</code> belongs to SwiftUI scenes. <code>UIHostingController</code> belongs to UIKit hosting. When those two worlds meet, UIKit lifecycle APIs are often the more dependable source of truth.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>References</h2><ul><li><p><a href="https://developer.apple.com/documentation/SwiftUI/ScenePhase?utm_source=chatgpt.com">Apple Developer Documentation: </a><code>ScenePhase</code><a href="https://developer.apple.com/documentation/SwiftUI/ScenePhase?utm_source=chatgpt.com">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/scenephase">Apple Developer Documentation: </a><code>EnvironmentValues.scenePhase</code><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/scenephase">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/swiftui/migrating-to-the-swiftui-life-cycle?utm_source=chatgpt.com">Apple Developer Documentation: Migrating to the SwiftUI life cycle.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiapplication/didbecomeactivenotification?utm_source=chatgpt.com">Apple Developer Documentation: </a><code>UIApplication.didBecomeActiveNotification</code><a href="https://developer.apple.com/documentation/uikit/uiapplication/didbecomeactivenotification?utm_source=chatgpt.com">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiapplication/willresignactivenotification">Apple Developer Documentation: </a><code>UIApplication.willResignActiveNotification</code><a href="https://developer.apple.com/documentation/uikit/uiapplication/willresignactivenotification">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiscene/didactivatenotification?utm_source=chatgpt.com">Apple Developer Documentation: </a><code>UIScene.didActivateNotification</code><a href="https://developer.apple.com/documentation/uikit/uiscene/didactivatenotification?utm_source=chatgpt.com">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiscene/willdeactivatenotification?utm_source=chatgpt.com">Apple Developer Documentation: </a><code>UIScene.willDeactivateNotification</code><a href="https://developer.apple.com/documentation/uikit/uiscene/willdeactivatenotification?utm_source=chatgpt.com">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiscene/didenterbackgroundnotification">Apple Developer Documentation: </a><code>UIScene.didEnterBackgroundNotification</code><a href="https://developer.apple.com/documentation/uikit/uiscene/didenterbackgroundnotification">.</a></p></li><li><p><a href="https://developer.apple.com/documentation/uikit/uiscene/willenterforegroundnotification">Apple Developer Documentation: </a><code>UIScene.willEnterForegroundNotification</code><a href="https://developer.apple.com/documentation/uikit/uiscene/willenterforegroundnotification">.</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Swift Bits: XCTest Migration to Swift Testing]]></title><description><![CDATA[XCTest -> Swift Testing]]></description><link>https://antongubarenko.substack.com/p/swift-bits-xctest-migration-to-swift</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-xctest-migration-to-swift</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Fri, 08 May 2026 05:08:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b552dc87-8b3a-40ff-a642-5a66b178e8c5_379x386.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I decided to store Swift Bits notes on Substack as well. Searching on LinkedIn is pretty difficult because of the focus on the feed and the huge amount of content there. Plus, on Substack I can share extra links and references &#128578;</p><div><hr></div><p>Examining, checking, validating, and testing are essential parts of a developer&#8217;s life. Whether we&#8217;re verifying business logic, complex client-side computations, or trying to isolate a bug &#8212; tests are our helpers.<br><br>I&#8217;m not just talking about unit or UI tests. When we check an input and know the expected output, isn&#8217;t that a test? Maybe not as formal as Red&#8211;Green&#8211;Refactor or TDD (yes, I&#8217;m reading the book everyone&#8217;s been talking about lately), but it can still be seen as part of that approach.<br><br>For those who still use XCTest (like me &#128517;), as part of Swift Bits, I&#8217;m sharing some migration tips with you.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yHh1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yHh1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 424w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 848w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 1272w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yHh1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png" width="1352" height="1986" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1986,&quot;width&quot;:1352,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:492121,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/196835204?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yHh1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 424w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 848w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 1272w, https://substackcdn.com/image/fetch/$s_!yHh1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3f417-c1c0-4b3f-9ce9-bf610f335ad4_1352x1986.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Useful links:</p><ul><li><p><a href="https://developer.apple.com/xcode/swift-testing/">Apple Swift Testing Notes</a></p></li><li><p><a href="https://developer.apple.com/documentation/testing">Apple Developer: Swift Testing</a></p></li><li><p><a href="https://developer.apple.com/videos/play/wwdc2024/10179/">WWDC24: Meet Swift Testing</a></p></li><li><p><a href="https://developer.apple.com/documentation/testing/migratingfromxctest">Apple Guide: Migration from XCTest</a></p></li><li><p><a href="https://useyourloaf.com/blog/migrating-xctest-to-swift-testing/">UseYourLoaf: Migration to Swift Testing</a></p></li><li><p><a href="https://www.avanderlee.com/swift-testing/modern-unit-test/">Swift Testing: Writing a Modern Unit Test</a><a href="https://developer.apple.com/videos/play/wwdc2024/10179/"><br></a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Q&A: Swift Concurrency - Formatted]]></title><description><![CDATA[Direct live answers from Apple Developers Team]]></description><link>https://antongubarenko.substack.com/p/q-and-a-swift-concurrency-formatted</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/q-and-a-swift-concurrency-formatted</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Tue, 28 Apr 2026 05:42:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4d52ce32-9dc8-4338-8b84-f5e850c4fffd_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Swift Concurrency is a topic that doesn&#8217;t just appear occasionally &#8212; it&#8217;s more like a quiet whisper you hear all the time. Articles keep coming out, guides are recorded, assumptions get challenged&#8230; This has led to a <a href="https://www.youtube.com/watch?v=E95agtPgaa0&amp;t=3468s">new format</a> (or rather, an evolution of Apple&#8217;s format, if you get it), where you can ask questions live and get answers directly from Apple engineers across Foundation, SwiftUI, and other teams. So our beloved Q&amp;A section from Code-along sessions has now moved into a separate session.</p><p>The flow looks like this:</p><ul><li><p>Log in with your Apple Developer Forum account</p></li><li><p>Submit a question for moderation (yes, moderation again!)</p></li><li><p>Pass moderation</p></li><li><p>Get upvotes to increase your chances of being answered</p></li></ul><p>I took a chance and asked about task cancellation best practices &#8212; a topic many others were also interested in.</p><p>And, as usual, all questions were later removed. Thankfully, the answers were captured during the live session. To help you, I prepared a transcript for those who prefer not to watch, and added timestamps for those who prefer not to read &#128578;</p><p>Enjoy!</p><div><hr></div><h2>Is </h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;b498ba52-9b2e-4194-a684-1a3e39c4fcd5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Task { @MainActor in
   ...
 } </code></pre></div><h2>preferred over </h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;7b59c82f-d5e8-40a7-ac13-a97919770507&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Task { 
   // code to run in background
   await MainActor.run { 
     ...
   }
}</code></pre></div><p>The difference between those two approaches is that Task with <code>MainActor</code> runs the entire task body on the <code>MainActor</code>. Of course, some operations inside that Task might be isolated to something else, and the task can switch between different isolation domains depending on what the code calls. But, for example, if you have synchronous code inside the Task and you want all of it to happen on the <code>MainActor</code>, then I would write the <code>MainActor</code> annotation directly on the Task closure signature. If you only have a specific operation, maybe just a couple of lines of code inside the Task, that you want to run on the <code>MainActor</code>, but everything else should continue running in the same context where the Task was created, then that is a case where you can use <code>MainActor.run</code>. Another option is to factor that code out into a <code>MainActor</code>-isolated function and call that function from within the task.</p><p><a href="https://youtu.be/E95agtPgaa0?t=478">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the difference between <code>nonisolated</code> and <code>nonisolated(nonsending)</code>, and why was the latter introduced?</h2><p>Nonisolated means that a declaration has no preference for isolation. It does not have a fixed isolation, so it can be used in multiple isolation contexts. Nonisolated non-sending is about asynchronous functions. There was a behavior originally implemented for asynchronous functions in Swift concurrency where asynchronous functions would move to the concurrent thread pool, the shared executor, by default. We found that this was not the right trade-off as people adopted Swift concurrency and we learned more. So nonisolated non-sending, which is now the default behavior if you opt in using the approachable concurrency settings, is the new behavior where your asynchronous task or asynchronous function will not leave the isolation it was called from right away, unless there is an explicit reason to.</p><p><a href="https://youtu.be/E95agtPgaa0?t=563">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When marking a ViewModel with <code>@MainActor</code> how should we handle a method inside it that performs heavy I/O processing? How do process outside of MainActor/Main Thread?</h2><p>Yes, if you have a particular operation on any MainActor-isolated type and you want only that operation to be offloaded, you can annotate just that method with a different isolation. For example, if you want to offload it to the concurrent thread pool, you can now use the <code>@concurrent</code> attribute. That is the right explicit spelling for it. The compiler will prevent you from directly accessing any MainActor mutable state on that view model, or whatever your MainActor-isolated type is, to make sure you are not accessing that state at the same time as another operation on that type is accessing the same mutable state from the MainActor. So take a look at @concurrent. There are also a lot of references from last year&#8217;s WWDC, where many uses of <code>@concurrent</code> were covered as well.</p><p><a href="https://youtu.be/E95agtPgaa0?t=643">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When a <code>Task</code> is created from <code>@MainActor</code>, will that block the main thread and cause UI hitches?</h2><p>A task being created from <code>MainActor</code> is something you probably encounter often when working on a UI framework. I think that when a task is created on <code>MainActor</code>, there is still a suspension point that the compiler creates, so while that work is being performed, the main thread can still continue working on UI updates and avoid causing hitches. In general, the task machinery tries to schedule your work on the concurrent thread pool so that it does not block excessively. That is one of the nice behaviors that tasks provide. I think it depends on what the task does. If you create a task that is isolated to <code>MainActor</code> and that task starts running on <code>MainActor</code>, and you are doing some very expensive work that takes a long time, then it might block <code>MainActor</code>, and you might notice that. In that case, you may want to profile that code in Instruments and then decide whether to insert some suspension points or offload parts of that expensive task from <code>MainActor</code>. I really recommend that you do not worry about proactively offloading everything you can from <code>MainActor</code>. There are some things that are totally fine to run on <code>MainActor</code>. But if you notice hangs in your app and identify that piece of code through Instruments or your profiling tool of choice, that is when you should make targeted changes to move the expensive work off.</p><p><a href="https://youtu.be/E95agtPgaa0?t=720">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the best practice to cancel a task?</h2><p>I think the practice I have been using is storing a task on the type and then calling cancel on it when needed. That is a good approach if you are trying to granularly manage the lifetime of that task. Any task that you want to be able to cancel needs to be tracked in some way, so you need to hold on to the handle.</p><p><a href="https://youtu.be/E95agtPgaa0?t=849">&#9201;&#65039;Time code</a></p><div><hr></div><h2>When should I reach for at <code>concurrent</code> instead of <code>nonisolated</code> async? Is there a one sentence rule?</h2><p><code>@concurrent</code> is guaranteed to run on the shared thread pool in the background. For <code>nonisolated async</code>, it depends on your upcoming feature settings. As described earlier, the one-sentence explanation is that you should use <code>@concurrent</code> when you want your async function to be offloaded to the concurrent thread pool. It is an explicit spelling for behavior that used to be implicit for async functions, but that behavior is changing because it was not the best trade-off and not the best default for async functions. So you should use <code>@concurrent</code> now when you want that behavior. With <code>nonisolated async</code>, it depends on whether you have the approachable concurrency upcoming features enabled in your Xcode project, which is the default for new Xcode projects. If you do not have it enabled, I recommend enabling those settings because it gives you a set of defaults that are a bit easier to use with the data safety diagnostics.</p><p><a href="https://youtu.be/E95agtPgaa0?t=900">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Is it possible to have too many actors? Is there an intrinsic limit? </h2><p>There is probably an intrinsic limit depending on how much memory you have, but I would definitely think about limiting how many different actors you have in your program. Generally speaking, you want to have just a few isolation domains in a program to reason about. Concurrent programming is hard, as we said earlier, and most of your code should be synchronous unless you have a reason to introduce concurrency and a reason to introduce different isolation domains. Every actor is a different isolation domain, and if actors need to work together, that creates complexity in your program. So you really want to think carefully when you factor something out into an actor, and decide whether it is really appropriate to add the complexity of needing to access that code asynchronously from other parts of your program. I think a very common question is: when do you actually use an actor? The answer is specifically when you can define that something will always need its own isolated domain, and the entirety of its functionality requires you to maintain that isolation. On the slide, I had a couple of examples like network caching or accessing a database. Those are common things where you often already know the work is going to be in an isolated domain anyway.</p><p><a href="https://youtu.be/E95agtPgaa0?t=997">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Could a Task started by <code>.task</code> modifier in SwiftUI view be cancelled if the view is refreshed by state change?</h2><p>The <code>task</code> modifier in SwiftUI cancels your task when the view disappears, and state changes usually do not cause your view to be fully destroyed by SwiftUI. State works in a way where, when the state changes, SwiftUI reevaluates the view&#8217;s body, but it does not actually destroy the view. One case where you may see this behavior is if you have an <code>if</code>statement in your view&#8217;s body, and one branch becomes true while the other branch previously contained a view with a task. Once the condition flips, SwiftUI would cancel the task because the view in the other branch of the <code>if</code> statement is destroyed. You can also think about it like <code>onDisappear</code>: <code>task</code> is a modifier, and the <code>task</code> modifier manages cancellation with the same timeline as the <code>onDisappear</code> modifier, if you have used that before.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1092">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Intuitively what does the nonisolated keyword mean? Especially after the recent changes.</h2><p>What has not really changed necessarily, but is now defaulted in some cases, is that <code>nonisolated</code> is a statement of your intent to not require any particular isolation for a type or function. When you annotate a type as <code>@MainActor</code>, you are stating that you only want it to be used from <code>MainActor</code>. <code>nonisolated</code> is removing that restriction. You can use it from any isolation domain, and that is really flexible and powerful. It is often the right choice for code in libraries, where you do not know where the app will want to use your library types. It can also be appropriate in your app for low-level models that do not have any inherent need to be isolated to <code>MainActor</code> or some other isolation domain. That is part of what motivated the behavior change for <code>nonisolated</code> with async functions. When something is marked as <code>nonisolated</code>, you can use it from anywhere. If you have a method that is not isolated and you call it from somewhere, it is going to run in the isolation domain it was called from. That used to not be the behavior for async functions, but now it is. So now there is a consistent meaning: when you see <code>nonisolated</code>, it means you can use it from anywhere, and when you call those methods, they are going to run wherever you call them from.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1183">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Are there plans to introduce tasks with timeouts (like GCD group timeout) or something to run after certain period of time (like <code>DispatchQueue.main.asyncAfter</code>)?</h2><p>There is actually a proposal under review right now that adds this exact API to <code>Task</code>. I think the naming is still under debate. Right now, it is called <code>withDeadline</code>, and it is meant to provide exactly that behavior: you run a task, and if it exceeds a certain amount of time, the task will be canceled. That behavior has also been getting a lot of feedback during review. If you are interested in checking it out, you can head over to the Swift Forums and look at the <a href="https://forums.swift.org/t/se-0526-withdeadline/85850">Swift Evolution proposal</a>. If you have any feedback about the proposed behavior, you can comment on the review thread there. Whoever asked that question should definitely go to the forums, ask questions, or provide feedback.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1284">&#9201;&#65039;Time code</a></p><div><hr></div><h2>For a new app target, should I adopt MainActor default isolation? Does your answer change for libraries?</h2><p>In Xcode 26, all new app targets use <code>MainActor</code> isolation by default. As someone from the SwiftUI team, I think that is an absolutely amazing choice for app targets because, in our UI framework, views are already on <code>MainActor</code>. If you have written a SwiftUI view and also have a model type, you may have noticed that it is often convenient to put that model on <code>MainActor</code> as well. With <code>MainActor</code> by default turned on, you do not have to do that manually. It makes UI code much easier to write and reason about, and you do not have to think as much about synchronizing your state because everything is on <code>MainActor</code>. But when it comes to libraries, my answer would change, depending on what the library does. If you are trying to be performant, concurrency can help you optimize behavior in your code by parallelizing operations, and libraries can make use of that in some cases. If you find that an operation is taking too long and you want to split it up, then it can make sense to introduce more concurrency, especially in library code. The same applies to app targets as well: you start on <code>MainActor</code>, and once you see potential for a performance optimization, or you profile the app and see the main thread being blocked by an expensive operation in your model, then you can consider adding concurrency annotations and moving things off <code>MainActor</code>. I also think many general-purpose library APIs are not specific to any particular isolation domain. Most of Foundation is not isolated, and clients of Foundation can choose where they want to use those APIs. For example, with Foundation, most APIs are not specific to any isolation, so most of them are <code>nonisolated</code>. There are a few APIs that are specific, such as <code>UndoManager</code>, which is heavily UI-focused and annotated with <code>MainActor</code>. For Foundation, it makes sense to keep most things <code>nonisolated</code> by default and add <code>MainActor</code> isolation for UI-related APIs. But when you are working on an app, it is almost the inverse: most of your things are on <code>MainActor</code>, and you may want to selectively offload work from there.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1344">&#9201;&#65039;Time code</a></p><div><hr></div><h2>If a class launches a long-running Task and calls <code>task.cancel()</code> in <code>deinit</code>, does the task actually stop executing? And is its memory released immediately after cancellation? Also, are tasks automatically cancelled when their owning scope is deallocated?</h2><p>Cancellation in Swift concurrency is cooperative. Depending on what the task is doing, that operation needs to check for cancellation. Usually, for a library method or similar API to support cancellation, the course of action is to throw a cancellation error. If you are running long synchronous code and someone cancels the task handle, that synchronous code is not going to be interrupted at any moment. That is actually an important behavior, because the work might need to perform some cleanup operation that must happen atomically without being interrupted by something like task cancellation. So no, the task does not necessarily stop running immediately because of cancellation. But as soon as you hit a piece of code that handles cancellation and maybe throws a cancellation error, that is when it will usually return by throwing such an error. There was another question about whether tasks get cancelled when their owning scope is deallocated. If a class has a task handle, like we discussed earlier, and you want to track a task to cancel it, you need to store it as a property. But no, it is not automatically cancelled just because the owning scope is deallocated. That is one of the gotchas when you opt into managing your task&#8217;s lifetime: you always have to remember to cancel it. That is also good in the sense that you are being explicit about the task&#8217;s lifetime. There was also a question about tasks swallowing errors. A <code>Task</code> does not require handling of thrown errors, which can lead to silently ignored failures. There is now an accepted proposal that addresses that exact pain point. In Swift 6.4, there will be a new diagnostic for exactly that case, where you have a task and have not handled errors within it. There are different ways to handle errors within a task. You can handle any thrown errors inside the task body, or if the task itself can throw and you want to keep a handle to the task, you can wait until you get the task&#8217;s value and handle any potential errors that were thrown. Those are two different ways to handle task errors. So yes, with the acceptance of that proposal, you will now get diagnostics for this. If you are interested, you can check the Swift Forums or Swift.org Swift Evolution for the accepted proposed behavior.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1524">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the best practice for asynchronous requests in response to a button tap? Kicking off a one-off task? Exposing a synchronous method from your model that kicks off a task internally?</h2><p>The general recommendation, although you can structure your code in both ways, is that I would prefer moving that asynchronous code to your model. Then the model can respond back with synchronous output to your view, so the view stays responsive and the model handles all of that asynchronous work. The benefit of this approach is that you can also test the asynchronous work outside of your view, which brings greater benefits to your entire codebase.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1737">&#9201;&#65039;Time code</a></p><div><hr></div><h2>How do you handle legacy NSObject types which are non-scendable to work with at sendable closures?</h2><p>I do not have a specific recommendation for <code>NSObject</code> types in general. It depends a lot on the situation in your code. If you have a class that inherits from <code>NSObject</code>, and your class does not add any state, and neither does <code>NSObject</code> itself, then it is possible that the type does not have any state. If it does not have any state, or it protects its mutable state, then you can mark it as <code>@unchecked Sendable</code> because you are managing that state yourself. If you really do have mutable state in that class, I do not recommend capturing it inside a <code>@Sendable</code> closure. With region-based isolation, which was introduced with the Swift 6 compiler, there are other tools you can use when you need to pass a closure over an isolation domain, but you do not necessarily need to call that closure multiple times, potentially in parallel. You can use the <code>sending</code> modifier, which allows you to pass a closure to another isolation domain. This is what the <code>Task</code> initializer and task creation methods use, because that closure goes to a different isolation domain depending on where you want to run the task, but it is only called once and only sent over an isolation domain one time. That allows you to capture non-<code>Sendable</code> state, like classes with mutable state or classes that inherit from <code>NSObject</code>, as long as you do not use them again from the original isolation domain where you formed the closure. Depending on the pattern of your code, there may be different solutions. There is not one general piece of advice for all classes that inherit from <code>NSObject</code>, but there are a variety of tools you can use depending on the exact concurrency guarantees of your code. Another tool we have used in Foundation is to avoid sharing the entire <code>NSObject</code> when you only need pieces of it. Often, an <code>NSObject</code> may have properties like a string or integer, which are <code>Sendable</code>types. So while you may not be able to share the whole <code>NSObject</code> between isolation domains, you can pull out the pieces that you need to share, as long as you do not need to worry about mutations. That has helped us avoid making everything <code>Sendable</code>. Instead, we can pick out the pieces that really need to be shared, make those <code>Sendable</code>, and let the legacy code that encapsulates everything remain non-<code>Sendable</code>. Also, when dealing with <code>@Sendable</code> closures and captured state, it is often helpful to use the closure&#8217;s capture list to capture only the <code>Sendable</code> pieces of your state, instead of trying to send over the entire containing type, which may not be <code>Sendable</code>. Hopefully, that provides some insight, but it is also worth checking the documentation and Swift Forums, where there are often many different tactics and suggestions discussed.</p><p><a href="https://youtu.be/E95agtPgaa0?t=1793">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is the best practice to write a unit test for async await code?</h2><p>You can use Swift Testing and make your test method async, then test it there. If you need that method to run with a particular isolation, you can mark the test itself or the entire test case type with <code>MainActor</code>, or with whatever isolation you need that test to run on. Swift Testing is definitely worth checking out if you have not already.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2014">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is best use case of <code>Task.detached</code>?</h2><p>I have seen a lot of questions recently about when to use <code>Task.detached</code> versus when to use <code>Task { @concurrent in }</code>, because now those two things have very similar behaviors. The main difference is that the plain <code>Task</code> initializer, even when you specify some isolation, still inherits certain things from the surrounding context. Obviously, if you have explicitly specified an isolation, it does not inherit isolation, but it does inherit priority from the surrounding context. <code>Task.detached</code>, on the other hand, is completely detached. Nothing from the surrounding context is inherited by that task. So if you want something that is completely detached from the surrounding context, use <code>Task.detached</code>. If you just want to control the isolation, but you still want those other things to be inherited, use <code>Task { @concurrent in }</code>.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2057">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the recommended pattern when processing many (thousands) of files, not just &#8220;spawn a task for every item&#8221;?</h2><p>Profile first, because you never really want to just theorize about what might improve your performance the most. Experiment, profile, and see what is actually working. I could imagine that spawning a task for every file might not be the right design. Maybe you want to have some kind of batching logic instead. But you could try the most straightforward approach first and see how it works out before doing something more complicated.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2117">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Async Task from a SwiftUI view that calls a shared <code>@Observable</code> service. If the user dismisses the view before the task finishes, what&#8217;s the way to cancel it so the <code>URLSession</code> request is actually dropped (not just abandoned) and no UI state mutation happens post-dismiss?</h2><p>In SwiftUI&#8217;s <code>task</code> modifier, SwiftUI does not currently expose a way to give you the task handle so you can cancel the task owned by SwiftUI. But since <code>task</code> is effectively similar to <code>onAppear</code> starting a task, you could structure your code so that you own that task yourself. Then, within <code>onAppear</code> of your SwiftUI view, you can start it, keep the task handle, and cancel it yourself when needed. If the condition is that the user dismisses the view before the task is finished, then you may also be able to use the <code>task</code> modifier directly, because SwiftUI cancels the task spawned there when the view is dismissed. So it depends on what your expectation is. But there is always a way to use your own <code>Task</code> instance that is not owned by SwiftUI if you need more granular control.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2176">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What are Apple&#8217;s recommended best practices for exposing data from actors in SwiftUI? Creating a &#8220;proxy&#8221; data broker between the actors and UI adds a lot of boilerplate, requires my own notification mechanism when actor data changes (I believe).</h2><p>Exposing data from actors in SwiftUI depends on how that proxy object looks. But as we discussed earlier, introducing actors into your code adds extra synchronization points, where you have to <code>await</code> certain pieces. To access actor state from SwiftUI&#8217;s <code>MainActor</code> isolation, you need to introduce a layer that handles that synchronization. So it is almost inevitable that some kind of layer has to be introduced to synchronize that state. It would be helpful to hear more about the specifics, especially what boilerplate is involved here, what the notification mechanism looks like, and how the architecture is set up. This is probably a good case to continue the conversation on the Swift Forums, or file feedback with a sample project that has a similar architecture. That would make it easier to look more closely at the setup and see whether there is room for improvement to make this easier.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2287">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Any plans to support defers in async contexts?</h2><p>There is another accepted proposal for Swift 6.4 that lifts the limitation on async code in <code>defer</code> blocks. There is no special syntax in a <code>defer</code> block. You can just call async methods, and it works. So yes, this is another recently accepted proposal. These questions are pointing directly to Swift Evolution. There is a dashboard on swift.org where you can filter by implemented proposals for Swift 6.4, if you are curious and want to see the full list of proposals coming in Swift 6.4.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2404">&#9201;&#65039;Time code</a></p><div><hr></div><h2>If we are currently using Swift 5, have approachable concurrency set to no, default actor isolation is nonisolated and strict concurrency checking is minimal. What is the best first steps for us to move to Swift 6 concurrency?</h2><p>The first step I would recommend is enabling the approachable concurrency features. There is migration tooling you can use from the command line, and the Swift Migration Guide covers how to do this, especially in cases where you rely on the old behavior for async functions. If you are using that behavior extensively in your project, there is a straightforward way to maintain it: write <code>@concurrent</code> on those async functions. So there are mechanical ways to migrate to those upcoming features without changing the behavior of your code. The approachable concurrency features are meant to make it so there are some data safety errors you do not have to deal with. Part of their purpose is to make the defaults reduce concurrency unless you have explicitly introduced it yourself. After that, you can either change strict concurrency checking from minimal to complete, or you can go feature by feature. In Xcode build settings, there is a section with specific upcoming features where you can enable specific categories of data-race safety diagnostics. For example, static variables are generally unsafe if they are not isolated and are mutable from anywhere. You can enable just those diagnostics, deal with all your static variables, and then move on to the next category. If you prefer working that way, you can deal entirely with the same kind of problem, find the few solutions that apply, fix those cases, enable that category, and then move on. Another approach is to turn everything on first, but that can produce a lot of diagnostics and feel overwhelming. In Foundation, for example, adoption has happened target by target. Some simpler targets could enable more features earlier because there were fewer issues to work through. In other targets, specific upcoming feature flags were enabled either in the Xcode project or Swift package manifest. That makes it possible to enable diagnostics the team understands and has already decided how to handle, while leaving other areas turned off until there is a clearer approach. SwiftUI adoption followed a similar pattern. When you see a large number of warnings, it helps to look for patterns. Once you identify a recurring issue, you can think about a more general solution and apply it in multiple places. For example, marking something with <code>MainActor</code> or adding a missing <code>Sendable</code> annotation can make many warnings disappear because all the call sites of that code become concurrency-safe. A common misconception is that you have to refactor everything into a perfect concurrency model immediately. In reality, looking at the diagnostics feature by feature lets you work iteratively and focus on specific patterns that usually have only one or two real fixes. Once your current app architecture works with native Swift concurrency, you can start thinking more deeply about how to express that architecture: where to introduce isolation domains, how to break down models, and which properties or components have different concurrency stories.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2454">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Does MainActor guarantee main thread? If not, when does it use another thread?</h2><p>On our systems, <code>MainActor</code> is the main thread for sure. You could imagine an environment that Swift compiles for where <code>MainActor</code> might technically be some other thread. There are programs that are not apps where that might make sense, but for most intents and purposes, <code>MainActor</code> means the main thread. There are also some cases where you can have something annotated as <code>MainActor</code>, but it is still possible to call it from off <code>MainActor</code> in a context where the call site does not have strict concurrency checking enabled. I think the case where people see this most frequently is when interoperating with C, Objective-C, or C++. Those languages do not have static data safety, so it is possible to get a data race if you call something that is <code>MainActor</code>-isolated from one of those contexts. Swift also has tools to insert dynamic checking if you want to catch cases like that at runtime.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2751">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What&#8217;s the best practice to handle async shutdown functions during app termination? OS Lifecycle methods (such as <code>applicationWillTerminate</code>) are synchronous and they expect you to wrap up work there. Is it safe to use a semaphore in that scenario or are there better alternatives?</h2><p>Generally speaking, you do not want to use semaphores to bridge asynchronous code to synchronous code, because it is likely to result in deadlock. If you block on an asynchronous method using a semaphore, and the concurrent pool blocks on that, it is quite likely that you can use up all the resources, and suddenly nothing can make forward progress. So I do not recommend that. The situation you are describing is tough. You are in a situation where you have been given a synchronous callback, and it is the only interface you have. Trying to refactor as much code as possible that you need to handle in that context so that it is synchronous itself is probably the best approach. Obviously, that can be really difficult in some situations, but I would not recommend using a semaphore to solve that problem.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2830">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What is an executor? Is that exposed to us in Swift?</h2><p>You can make use of them, but you do not have to. Actors have default executors. The default executor manages scheduling for tasks, or more precisely, the synchronous pieces of a task, which are called jobs. Actors effectively have a priority queue of jobs that need to run on the actor from various tasks, and the executor handles scheduling and running those jobs. By default, that happens on the concurrent thread pool. You can also implement your own custom executors by conforming to a protocol and implementing whatever scheduling you need for your actor. Then you can provide a method on the actor that creates an instance of your custom executor type if you want to control that behavior. But the defaults are there so that you only need to drop down into that behavior if you need very fine-grained control over how those tasks are scheduled or run, or if you want to manage your own custom thread pool. That is something people sometimes do in services. So yes, those tools are available, but there are sensible defaults when you do not need that kind of control.</p><p><a href="https://youtu.be/E95agtPgaa0?t=2906">&#9201;&#65039;Time code</a></p><div><hr></div><h2>What does a healthy migration path from GCD and OperationQueue to Swift concurrency look like in a large existing app?</h2><p>On Foundation, we have experienced a lot of these migrations, moving from things that use GCD, OperationQueue, run loops, and other mechanisms. I think a healthy migration is first and foremost an incremental migration. As mentioned earlier, trying to change the architecture of your app at the same time as you adopt these features is not necessarily easy, and it may also be difficult for other members of your team to review. An incremental migration means looking at the architecture your app has today and starting with the parts that are already easy to express using the compiler tools we have now. For example, when looking at how Foundation could adopt concurrency attributes, the first step was to identify types or functionality where the implementation was known and where it was trivial to add a few annotations to express things to the compiler. Once that base layer is done, you can start working on the harder parts. That helps you work piece by piece, instead of trying to fix a simple missing <code>Sendable</code> annotation while also figuring out how to rearchitect something else. In some cases, it was simple enough to add annotations. For example, <code>UndoManager</code> is heavily UI-related, so it made sense to make it <code>MainActor</code>-bound. Other types simply received a <code>Sendable</code> annotation because they were simple Swift value types. But there are also things where you may need to think about a different approach. For example, <code>NotificationCenter</code> was not, strictly speaking, easy to use with async APIs, so we reimagined how <code>NotificationCenter</code> could work with new APIs. The goal was to structure it so that you can use new async APIs while still integrating with older legacy APIs, sometimes using unsafe escape hatches to cross the boundary between them. Working incrementally lets you start with the easy things, enable the warnings you can fully resolve early so you do not accidentally backpedal, and then look at your architecture to decide how more complex concepts should map into Swift concurrency. It takes time. Foundation is still in the process of adopting some of these features as new compiler features appear and people propose new suggestions on the Swift Forums. But over time, piece by piece, you can get to a place where complex interactions that were previously expressed through threads, queues, or run loops become much easier to understand because they are now expressed directly in Swift code, rather than hidden in documentation comments or notes. There is some upfront burden when deciding how to rethink something that was heavily OperationQueue-based or GCD-based in the Swift concurrency world. But once you have APIs and architecture that integrate nicely with Swift concurrency, it becomes much easier to build on top of them. Having a base layer that works with Swift concurrency also makes it easier for apps using Foundation to integrate with new SwiftUI features or other APIs that take advantage of Swift concurrency. As the ecosystem moves further into a world where these ideas can be expressed clearly in code, those APIs start to fit more naturally with async/await and other Swift concurrency APIs from Apple SDKs and third-party packages. One additional recommendation is to prioritize areas where you are writing new code. If some part of your codebase is in maintenance mode, has been around for a long time, and only receives occasional bug fixes, it is okay not to dive in and rewrite it wholesale just to modernize it. The most important goal is to reach a point where you can write new code using Swift concurrency features and express that new code with static data-safety guarantees. Legacy code can be revisited over time when you have a specific motivation to do that work.</p><p><a href="https://youtu.be/E95agtPgaa0?t=3019">&#9201;&#65039;Time code</a></p><div><hr></div><h2>Does it make sense to use I have a generic capture in Tasks?</h2><p>It really depends on what you are trying to do. It is definitely popular to use <code>weak self</code> in this kind of situation. People often want to optimize for the case where they are worried about capturing a reference to <code>self</code> and keeping it alive for too long. If that is your concern, you might use <code>weak self</code> to make sure the task is not what keeps <code>self</code> alive for too long. But you really need to understand what you are trying to accomplish in that task. It might be appropriate for the task to keep <code>self</code> alive until it finishes, because you want to complete that work regardless of whether everything else in the system has released its references to <code>self</code>. It depends on the kind of work. For example, with a task kicked off in a view, you may not want the view to be held onto by the task for too long after it disappears. That might be a good place to use <code>weak self</code>. As with most things, it depends. One case to be especially careful with is infinitely running tasks. If there is no end to the task, then the task may be what keeps <code>self</code> around. That is also the case where you can inadvertently get into reference cycles, especially if you are also storing a handle to that task on <code>self</code>. But as long as you do not have that kind of situation, it may be appropriate not to use <code>weak self</code> and let the task keep <code>self</code> alive until the work finishes.</p><p><a href="https://youtu.be/E95agtPgaa0?t=3361">&#9201;&#65039;Time code</a></p><div><hr></div><h2>I have a generic function that takes a Codable object: T as a parameter, makes a network call to retrieve an array of those objects and then returns an array of T. This will generate an error in Swift 6 about conformance must be on main thread. <br>Whats the best way to avoid this?</h2><p>I suspect that isolated conformance might be coming into play here. Depending on the features of the target you are building, if you have <code>MainActor</code> isolation by default enabled, it might require that the <code>Decodable</code> conformance you pass is on the main thread, or that the conformance might be <code>MainActor</code>-isolated, but you are passing it to something that does not require that. I would check the documentation. The <code>SendableMetatype</code> protocol might be a good annotation to add to this API, depending on whether the conformance is the problem or whether the values being returned are the problem. That could mean using <code>SendableMetatype</code> on the type you are passing in, or perhaps using the <code>sending</code> keyword on the values you are returning, if you are just fetching them from the network, handing them off to the caller, and never touching them again. I suspect some of those keywords might help express the fact that you are taking a conformance to <code>Codable</code> and perhaps sending it to a different isolation to perform the network request, or taking the values created from that background isolation and sending them back to the calling isolation. In one of those directions, one of those keywords is likely to help resolve it. The context here is really valuable, so providing a sample through Feedback Assistant, checking the forums, and looking at the documentation would help narrow down the exact recommendation.</p><p><a href="https://youtu.be/E95agtPgaa0?t=3468">&#9201;&#65039;Time code</a></p><div><hr></div><h2><strong>&#127942; </strong>Acknowledgments</h2><p>A huge thank-you to everyone who joined and shared thoughtful, insightful, and engaging questions throughout the session. Your curiosity and input made the discussion truly rich and collaborative.</p><p>Finally, a heartfelt thank-you to the Apple employees for leading the session, sharing expert guidance, and offering clear explanations of app optimization techniques. Your contributions made this an outstanding learning experience for everyone involved.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>One More thing&#8230;</h2><p>Through years of mentoring, I&#8217;ve built a small bookshelf of titles that are worth reading &#8212; or at least being aware of. The Grokking series from Manning Publishing is one of them. The style, the language, and the illustrations all combine to explain complex topics in an easy and engaging way.</p><p>You&#8217;ll find yourself wanting to learn more about things that once felt intimidating or difficult to understand. One of the latest additions is <em><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Grokking Data Structures</a></em> by Marcello La Rocca.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vuaA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" width="851" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:851,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63335,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Sharing the link with you</a>.</p>]]></content:encoded></item><item><title><![CDATA[SwiftUI: Refreshable Task Cancellation Promo]]></title><description><![CDATA[Understand the Refreshable Modifier]]></description><link>https://antongubarenko.substack.com/p/swiftui-refreshable-task-cancellation-cfc</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swiftui-refreshable-task-cancellation-cfc</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Wed, 22 Apr 2026 06:44:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/83c44cc7-ae49-4988-906a-8d84f3ea2a74_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ajnu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ajnu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:634958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194712576?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ajnu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pull-to-refresh became a standard interaction in iOS apps long ago, so <code>.refreshable</code> feels like one of the most natural SwiftUI APIs. You attach the modifier, await some async work, and SwiftUI handles the native refresh behavior for you.</p><p>Apple introduced <code>.refreshable</code> during the WWDC21 SwiftUI updates, in the same release cycle as Swift concurrency and the <code>task</code> modifier. In that session, Apple presented it as the modern way to support pull-to-refresh on iOS and iPadOS, while <code>task</code> was described as async work tied to a view&#8217;s lifetime and automatically cancelled when that view disappears.</p><p>And that lifecycle detail is exactly where things start to get interesting. But let&#8217;s get back to declarations.</p><div><hr></div><h2>What is<code>.refreshable</code>?</h2><p>At its core, <code>.refreshable</code> gives a view an async refresh action:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;44b01018-8f90-4e4f-a84a-a4f75a864cbf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">List(items) { item in
    Text(item.title)
}
.refreshable {
    await reload()
}</code></pre></div><p>The system then connects that action to pull-to-refresh behavior where supported. Apple also notes that the action is propagated through the environment as a <code>RefreshAction</code>, which is why the feature integrates naturally with SwiftUI&#8217;s declarative model.</p><p>It feels like a callback, but it is more than that. It is SwiftUI-owned async work.</p><p><a href="https://open.substack.com/pub/antongubarenko/p/swiftui-refreshable-task-cancellation?r=21t43r&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">Full article here</a>.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[SwiftUI: Refreshable Task Cancellation]]></title><description><![CDATA[Understand the Refreshable]]></description><link>https://antongubarenko.substack.com/p/swiftui-refreshable-task-cancellation</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swiftui-refreshable-task-cancellation</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Wed, 22 Apr 2026 06:40:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a3ff8937-7596-428a-93a0-871cf91467b7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ajnu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ajnu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:634958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194712576?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ajnu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ajnu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60134ad8-58c0-4461-ac09-f1d105e28819_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pull-to-refresh became a standard interaction in iOS apps long ago, so <code>.refreshable</code> feels like one of the most natural SwiftUI APIs. You attach the modifier, await some async work, and SwiftUI handles the native refresh behavior for you.</p><p>Apple introduced <code>.refreshable</code> during the WWDC21 SwiftUI updates, in the same release cycle as Swift concurrency and the <code>task</code> modifier. In that session, Apple presented it as the modern way to support pull-to-refresh on iOS and iPadOS, while <code>task</code> was described as async work tied to a view&#8217;s lifetime and automatically cancelled when that view disappears.</p><p>And that lifecycle detail is exactly where things start to get interesting. But let&#8217;s get back to declarations.</p><div><hr></div><h2>What is<code>.refreshable</code>?</h2><p>At its core, <code>.refreshable</code> gives a view an async refresh action:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;44b01018-8f90-4e4f-a84a-a4f75a864cbf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">List(items) { item in
    Text(item.title)
}
.refreshable {
    await reload()
}</code></pre></div><p>The system then connects that action to pull-to-refresh behavior where supported. Apple also notes that the action is propagated through the environment as a <code>RefreshAction</code>, which is why the feature integrates naturally with SwiftUI&#8217;s declarative model.</p><p>It feels like a callback, but it is more than that. It is SwiftUI-owned async work.</p><div><hr></div><h2>A Simple Example</h2><p>Let&#8217;s show <code>ScrollView</code> with random items added with delay:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;b7aa9567-5bd1-422d-80cb-aa7a602f6280&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">import SwiftUI

struct LoadedItem: Identifiable {
    let id: Int
    let value: Int
}

struct RefreshCancellView: View {

    @State private var items: [LoadedItem] = []

    var body: some View {
        NavigationStack {
            ScrollView(.vertical, showsIndicators: false) {
                LazyVStack(spacing: 16) {
                    ForEach(items) { item in
                        Text(item.value, format: .number)
                    }
                }
            }
            .padding(16)
            .refreshable {
                await loadItems(fromRefresh: true)
            }
            .task {
                //Loading the initial list
                await loadItems()
            }
            .navigationTitle("Refreshable Example")
            .navigationBarTitleDisplayMode(.inline)
        }
    }

    func loadItems(fromRefresh: Bool = false) async {
        await MainActor.run { items.removeAll() }

        let count = Int.random(in: 20...40)

        for i in 0..&lt;count {
            try? await Task.sleep(for: .milliseconds(Int.random(in: 50...150)))

            //Checking cancelaltion
            if Task.isCancelled {
                print("Cancelled fromRefresh: \(fromRefresh)")
                return
            }

            let value = Int.random(in: 0...10_000)
            let newItem = LoadedItem(id: i, value: value)

            await MainActor.run {
                items.append(newItem)
            }
        }
    }
}</code></pre></div><p>On the surface, this looks reasonable. Clear the old values, load the new ones, append them as they arrive, and check for cancellation between steps.</p><p>But this code is actually a very good demonstration of the problem.</p><div><hr></div><h2>The Problem</h2><p>The key trigger is this line:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;17edec09-57d5-49a2-8d48-2d889aa65262&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">await MainActor.run { items.removeAll() }</code></pre></div><p>That mutation happens immediately inside the <code>.refreshable</code> task.</p><p>Since <code>items</code> is <code>@State</code>, SwiftUI redraws the body right away. And because the refresh work is owned by SwiftUI, that redraw can invalidate the task context that is currently running the refresh closure. The Stack Overflow discussion describes exactly this: clearing the data causes the body to redraw, <code>.refreshable</code> is redrawn too, and the previous task gets cancelled.</p><blockquote><p>And this could throw you into debugging session for a long of time&#8230;</p></blockquote><p>Then the example makes the situation even more fragile here:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;d1c21dff-a411-4188-b2e2-9bf40f59798e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">await MainActor.run {
    items.append(newItem)
}</code></pre></div><p>Every append is another state mutation, which means another redraw opportunity while the refresh task is still active.</p><p>So this sample now has both failure multipliers:</p><ul><li><p>one immediate redraw from <code>removeAll()</code></p></li><li><p>repeated redraws from every <code>append</code></p></li></ul><p>That is why it reproduces the issue so clearly.</p><div><hr></div><h2>Why This Happens</h2><p>The simplest mental model is this:</p><p><code>.refreshable</code> runs your closure as structured work owned by SwiftUI.</p><p>Apple&#8217;s WWDC explanation of the related <code>task</code> modifier is useful here: SwiftUI can attach async work to a view lifetime and cancel it when the view is removed. While Apple&#8217;s <code>.refreshable</code> docs are brief, the observed behavior lines up with that same lifecycle-driven model.</p><p>So when your <strong>refresh code changes</strong> the very state that drives the current body, SwiftUI may <strong>rebuild</strong> enough of the hierarchy to cancel the refresh-owned task before it completes. The Stack Overflow thread phrases it quite directly: clearing the observable collection redraws the body, <code>.refreshable</code> redraws too, and the previous task is cancelled.</p><p>This is also why the issue feels so strange. The code is not &#8220;wrong&#8221; in the usual sense. It is just colliding with the ownership model of SwiftUI concurrency.</p><div><hr></div><h2>A Common Symptom</h2><p>You pull to refresh, the spinner appears, work starts, and then one of these happens:</p><ul><li><p>the task stops halfway through</p></li><li><p>only part of the list loads</p></li><li><p>a network request suddenly throws cancellation (<strong>Code -999</strong> for the those who are trying to Google it)</p></li></ul><p>That half-finished feeling is often the first clue that the problem is not your API layer, but the view lifecycle interacting with structured concurrency.</p><h3>Fix 1: Avoid intermediate UI changes until the final update</h3><p>The cleanest fix is to avoid publishing intermediate mutations while the refresh is running.</p><p>Instead of clearing and appending directly into <code>@State</code>, collect results locally and assign once at the end:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;fdf97e8d-70b2-42fe-855c-171bd341ca32&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">func loadItems(fromRefresh: Bool = false) async {
    let count = Int.random(in: 20...40)
    var loaded: [LoadedItem] = []

    for i in 0..&lt;count {
        try? await Task.sleep(for: .milliseconds(Int.random(in: 50...150)))

        if Task.isCancelled {
            print("Cancelled fromRefresh: \(fromRefresh)")
            return
        }

        let value = Int.random(in: 0...10_000)
        loaded.append(.init(id: i, value: value))
    }

    await MainActor.run {
        items = loaded
    }
}</code></pre></div><p>This works better because SwiftUI sees only one final state change instead of dozens of redraw-triggering mutations.</p><p>The tradeoff is that you lose progressive updates during refresh. But in most real pull-to-refresh flows, that is completely acceptable. The user usually cares more about a stable completion than about watching rows arrive one by one.</p><h3>Fix 2: Move the Work Into an Unstructured Task</h3><p>The other workaround is to let <code>.refreshable</code> create a separate task and await that task&#8217;s value:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;3b278f33-ad6b-441a-b9d0-4860facc1491&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">.refreshable {
    await Task {
        await loadItems(fromRefresh: true)
    }.value
}</code></pre></div><p>This pattern appears directly in one of the Stack Overflow answers: wrapping the logic in <code>Task { ... }.value</code> allows the refresh spinner to remain visible while the new task continues even if the original refresh-owned task is cancelled.</p><p>That makes it a practical fix when redraw-driven cancellation is getting in the way.</p><p>There is also a looser variation:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;c0df5d02-7f8f-4260-bdfb-4dd5f34233b3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">.refreshable {
    Task {
        await loadItems(fromRefresh: true)
    }
}</code></pre></div><p>That decouples the work even more, but it also changes the semantics more aggressively because the refresh action no longer truly owns the work. The safer version is usually <code>await Task { ... }.value</code>, since it still lets the refresh indicator track completion.</p><div><hr></div><h2>Which Fix is Better?</h2><p>In most cases, the first fix is the better design.</p><p>Use the <strong>single final update</strong> approach when:</p><ul><li><p>you can build the refreshed data off to the side</p></li><li><p>progressive rendering is not important</p></li><li><p>you want to stay inside structured concurrency as much as possible</p></li></ul><p>Use the <strong>unstructured task wrapper</strong> when:</p><ul><li><p>redraw-driven cancellation is hard to avoid</p></li><li><p>intermediate observable changes are required</p></li><li><p>you need a practical workaround more than a pure structured model</p></li></ul><p>The Stack Overflow thread includes both ideas in different forms: avoid redraw-triggering updates until later, or wrap the refresh body in a new task so it survives the redraw.</p><div><hr></div><h2>Final Thoughts</h2><p>This is one of those SwiftUI behaviors that feels confusing until you frame it correctly.</p><p>If your <code>.refreshable</code> code updates the source of truth too early, the refresh task may cancel itself through the very redraw it caused. That is why the safest approach is often to keep all temporary work local and publish one final result when loading finishes.</p><p>And if you truly need incremental changes during refresh, then an unstructured task wrapper may be the most practical escape hatch.</p><p>That is a subtle detail, but it is the difference between a refresh flow that feels flaky and one that behaves predictably.</p><div><hr></div><h2>References</h2><ul><li><p><a href="https://developer.apple.com/documentation/swiftui/view/refreshable%28action%3A%29">Apple Developer Documentation:</a> <code>refreshable(action:)</code></p></li><li><p><a href="https://developer.apple.com/documentation/swiftui/refreshaction">Apple Developer Documentation:</a> <code>RefreshAction</code></p></li><li><p><a href="https://developer.apple.com/videos/play/wwdc2021/10018/">Apple WWDC21 session</a> &#8220;What&#8217;s new in SwiftUI,&#8221; including <code>refreshable</code> and lifecycle-bound async tasks.</p></li></ul><div><hr></div><h2>One More thing&#8230;</h2><p>Through years of mentoring, I&#8217;ve built a small bookshelf of titles that are worth reading &#8212; or at least being aware of. The Grokking series from Manning Publishing is one of them. The style, the language, and the illustrations all combine to explain complex topics in an easy and engaging way.</p><p>You&#8217;ll find yourself wanting to learn more about things that once felt intimidating or difficult to understand. One of the latest additions is <em><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Grokking Data Structures</a></em> by Marcello La Rocca.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vuaA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" width="851" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:851,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63335,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Sharing the link with you</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Swift Bits: 320 x 480 Window]]></title><description><![CDATA[Debugging strange UI]]></description><link>https://antongubarenko.substack.com/p/swift-bits-320-x-480-window</link><guid isPermaLink="false">https://antongubarenko.substack.com/p/swift-bits-320-x-480-window</guid><dc:creator><![CDATA[Anton Gubarenko]]></dc:creator><pubDate>Wed, 15 Apr 2026 08:21:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/da938462-733e-48ef-aab5-770a311ddff7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is a strange iOS behavior that feels almost archaeological at this point: if your app does not have a proper launch screen configured, the window can end up behaving as if the app&#8217;s size is 320 &#215; 480. Both for UIKit app with AppDelegate/SceneDelegate and SwiftUI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xirq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xirq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 424w, https://substackcdn.com/image/fetch/$s_!xirq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 848w, https://substackcdn.com/image/fetch/$s_!xirq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 1272w, https://substackcdn.com/image/fetch/$s_!xirq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xirq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png" width="448" height="974.0099502487562" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2622,&quot;width&quot;:1206,&quot;resizeWidth&quot;:448,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;User attachment&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="User attachment" title="User attachment" srcset="https://substackcdn.com/image/fetch/$s_!xirq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 424w, https://substackcdn.com/image/fetch/$s_!xirq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 848w, https://substackcdn.com/image/fetch/$s_!xirq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 1272w, https://substackcdn.com/image/fetch/$s_!xirq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96bde9f5-b7f8-445e-bcb2-33dcbb4644f7_1206x2622.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Facing a cutting-edge issue )</figcaption></figure></div><p>Not the actual screen size.</p><p>Not the simulator device size.</p><p>That old fallback size.</p><p>And yes, this behavior has been around for years. It still shows up in practice when launch-screen configuration is missing or broken, and developers keep rediscovering it because the symptom looks unrelated at first glance: your app launches, but the window, bounds, or rendered content behave like the device is tiny.</p><div><hr></div><h2>The strange part</h2><p>You remove the launch screen, or the app no longer points to the correct launch storyboard, and suddenly the app starts acting like it lives in a much older world.</p><p>A common symptom is that values like screen bounds appear as 320 &#215; 480, regardless of the actual device.</p><p>This is what makes the issue so confusing:</p><ul><li><p>the code that reads screen size may look correct</p></li><li><p>the simulator may be a modern iPhone</p></li><li><p>the UI may still compile and run</p></li><li><p>but the app behaves like it launched in a legacy compatibility path</p></li></ul><div><hr></div><h2>Why does this happen?</h2><p>The launch screen is not only a visual placeholder while the app starts. It is also part of how iOS understands that your app supports modern screen sizes and adapts correctly to the current device family and display characteristics.</p><p>Historically, when iOS could not determine that an app supported newer display formats, it could fall back to older sizing behavior. That is why 320 &#215; 480 keeps surfacing: it is the classic original iPhone/iPod logical size, and the system can still expose behavior that resembles that legacy compatibility mode when launch-screen support is absent or invalid.</p><div><hr></div><h2>It is not really about &#8220;splash screens&#8221;</h2><p>That is also why calling it &#8220;just a splash screen&#8221; is misleading.</p><p>On iOS, the launch screen does more than show a placeholder image. It is part of the app&#8217;s launch configuration, and missing it can affect how the app is treated at startup.</p><p>So the issue is not really visual.</p><p>It is architectural.</p><div><hr></div><h2>How to check if this is your problem</h2><p>If your app behaves suspiciously small, clipped, letterboxed, or reports old dimensions, check these first:</p><ul><li><p>does the target have a valid Launch Screen configured?</p></li><li><p>does Info.plist contain the correct UILaunchStoryboardName or equivalent launch-screen configuration?</p></li><li><p>does the referenced storyboard actually exist?</p></li><li><p>did the launch-screen file get renamed without updating the target settings?</p></li></ul><p>In practice, the fix is often simply restoring a valid LaunchScreen.storyboard and making sure the target points to it correctly.</p><div><hr></div><h2>The simplest fix</h2><p>In most native iOS projects, the practical fix is:</p><ol><li><p>Create or restore LaunchScreen.storyboard</p></li><li><p>Set it as the app&#8217;s launch screen in target settings</p></li><li><p>Ensure the app bundle includes it</p></li><li><p>Clean and rebuild the app</p></li></ol><p>Typical config:</p><pre><code><code>&lt;key&gt;UILaunchStoryboardName&lt;/key&gt;
&lt;string&gt;LaunchScreen&lt;/string&gt;</code></code></pre><p>If you are using SwiftUI, plist-based launch-screen configuration is also possible in newer setups, but the core point stays the same: the app needs a valid launch-screen definition. Even the empty ones fixing it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9ZQ0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 424w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 848w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 1272w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png" width="1456" height="654" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:654,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:291167,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 424w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 848w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 1272w, https://substackcdn.com/image/fetch/$s_!9ZQ0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda3a6d3-182f-4a48-9d02-f0f9638433dc_1702x764.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Info Tab in Target Settings - Launch Screens</figcaption></figure></div><p>For Storyboard, you can check:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UlPM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UlPM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 424w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 848w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 1272w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UlPM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png" width="1456" height="659" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:659,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:288939,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UlPM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 424w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 848w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 1272w, https://substackcdn.com/image/fetch/$s_!UlPM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F826bb72c-cac3-4460-ac14-f474e3f68df5_1676x758.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Info Tab in Target Settings - Single Storyboard</figcaption></figure></div><p>And it&#8217;s fine again!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ep8O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ep8O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 424w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 848w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 1272w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ep8O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png" width="448" height="974.0099502487562" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2622,&quot;width&quot;:1206,&quot;resizeWidth&quot;:448,&quot;bytes&quot;:3441618,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ep8O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 424w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 848w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 1272w, https://substackcdn.com/image/fetch/$s_!Ep8O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42113eb3-3ffd-4c2e-914a-4d1293857037_1206x2622.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Weather is fine )</figcaption></figure></div><div><hr></div><h2>Why this is still surprising in 2026</h2><p>Because it feels like the kind of issue that should have disappeared with older iPhone screen transitions.</p><p>But launch behavior sits in a compatibility-sensitive part of the platform, and when that configuration is missing, iOS can still fall back in ways that expose very old assumptions. That is why this bug feels so strange: it does not look like a missing resource problem. It looks like the whole app forgot what year it is.</p><div><hr></div><h2>References</h2><ul><li><p>Apple Developer Documentation: <a href="https://developer.apple.com/documentation/bundleresources/information-property-list/uilaunchstoryboardname?utm_source=chatgpt.com">UILaunchStoryboardName</a></p></li><li><p>Apple Developer Documentation: <a href="https://developer.apple.com/documentation/xcode/specifying-your-apps-launch-screen?utm_source=chatgpt.com">Specifying your app&#8217;s launch screen</a></p></li><li><p>Apple Human Interface Guidelines: <a href="https://developer.apple.com/design/human-interface-guidelines/launching?utm_source=chatgpt.com">Launching</a></p></li><li><p>Apple Developer Documentation: <a href="https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/LaunchImageType.html?utm_source=chatgpt.com">UILaunchScreen</a></p></li><li><p>Apple Technote: <a href="https://developer.apple.com/documentation/technotes/tn3118-debugging-your-apps-launch-screen?utm_source=chatgpt.com">Debugging your app&#8217;s launch screen</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://antongubarenko.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>One More thing&#8230;</h2><p>Through years of mentoring, I&#8217;ve built a small bookshelf of titles that are worth reading &#8212; or at least being aware of. The Grokking series from Manning Publishing is one of them. The style, the language, and the illustrations all combine to explain complex topics in an easy and engaging way.</p><p>You&#8217;ll find yourself wanting to learn more about things that once felt intimidating or difficult to understand. One of the latest additions is <em><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Grokking Data Structures</a></em> by Marcello La Rocca.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vuaA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg" width="851" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:851,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63335,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://antongubarenko.substack.com/i/194161391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vuaA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vuaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5eb50d14-7bb4-455c-9006-33f2bacfb5c8_851x315.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.manning.com/books/grokking-data-structures?utm_source=AG-DEV&amp;utm_medium=affiliate&amp;utm_campaign=book_larocca3_grokking_9_28_23&amp;a_aid=AG-DEV&amp;a_bid=89b98f8d&amp;chan=sbs">Sharing the link with you</a>.</p>]]></content:encoded></item></channel></rss>