<?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[StuVision: Exploration of Apple's Vision Pro]]></title><description><![CDATA[Forays into the new VisionOS platform from a developer's perspective]]></description><link>https://varrall.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!PG14!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ebd42a-f2a8-4f2e-8de1-b354406c0a4f_1280x1280.png</url><title>StuVision: Exploration of Apple&apos;s Vision Pro</title><link>https://varrall.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 14 Apr 2026 02:00:31 GMT</lastBuildDate><atom:link href="https://varrall.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Stuart Varrall]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[varrall@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[varrall@substack.com]]></itunes:email><itunes:name><![CDATA[Stuart Varrall]]></itunes:name></itunes:owner><itunes:author><![CDATA[Stuart Varrall]]></itunes:author><googleplay:owner><![CDATA[varrall@substack.com]]></googleplay:owner><googleplay:email><![CDATA[varrall@substack.com]]></googleplay:email><googleplay:author><![CDATA[Stuart Varrall]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[More windows approach for Vision Pro]]></title><description><![CDATA[How many windows is too many?]]></description><link>https://varrall.substack.com/p/more-windows-approach-for-vision</link><guid isPermaLink="false">https://varrall.substack.com/p/more-windows-approach-for-vision</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Fri, 17 Nov 2023 05:00:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kkc5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I have been trying to improve the flexibility of my Cricket Scores App to show more even more information about a game at once. Coming from iOS, the initial idea was to have just have one big window that shows everything, but from experience using a Vision Pro, that just feels limiting on device. In Apple&#8217;s own words, Vision Pro is an &#8220;infinite canvas&#8221; so why not make use of that space better.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kkc5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kkc5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kkc5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png" width="1456" height="1091" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1091,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4460156,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kkc5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!kkc5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0394be2a-09eb-405d-a9f8-93e8034cba3a_2732x2048.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">Five windows just to watch Cricket</figcaption></figure></div><p>One of the things that I was really impressed with during the hands on was the ability to resize and position windows in a very natural and intuitive way. This is in contrast to how it works in the simulator which is a very clunky and frustrating experience trying to position things, particularly at specific depths or orientations. This is one area that the simulator fails to simulate the on-device experience.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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>I have been exploring having different independent windows showing information, as well as content better adapting to the resizing of windows. For example, if a user wants to resize the main game window, which usually shows the run worm and ball-by-ball information, it adapts to being short and wide by showing a simple game banner.</p><p>I&#8217;m also experimenting with having the ability to pop-out this content to have both views visible at once. Whether this will be a useful approach or too confusing in practice remains to be seen. This approach takes more work, but is a better experience than either artificially restricting the size of the window, or trying to compress a lot of information into a small 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_!f4R-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f4R-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 424w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 848w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 1272w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f4R-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png" width="864" height="452" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:452,&quot;width&quot;:864,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:250607,&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;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!f4R-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 424w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 848w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 1272w, https://substackcdn.com/image/fetch/$s_!f4R-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e2898eb-d7a5-4b02-9442-8a0ba93a8d83_864x452.png 1456w" sizes="100vw"></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">Ornament with a button, to &#8220;pop-out&#8221; a new window</figcaption></figure></div><p>Practically the way this is achieved is liberal use of <a href="https://developer.apple.com/documentation/swiftui/viewthatfits">ViewThatFits</a>, to provide alternative views for different sized windows. When using the pop-out window button, I set a initial display size that&#8217;s appropriate for the header view, which currently is actually the exact same view as the game window, so it&#8217;s possible for the user to make this bigger, and it will revert to the full game detail as appropriate.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;c43a5b7e-6820-4a08-94ce-b5374d1f1835&quot;,&quot;duration&quot;:null}"></div><p>An extension of this approach is what happens if the window gets really large, should it add even more information, that&#8217;s something else I want to play around with going forwards. Perhaps a single window with everything self contained will actually work better in practice, but my feeling in that case is that users will want the ability to customise the layout, perhaps hide and resize views within a window, which seems like a lot of overhead to manage. It would effectively be a window manager within the App itself.</p><p>In addition to content adapting to the window size, there are two other screens of information which can all be open simultaneously for a game. This way users can open the information they are interested in, as well as position and resize it as they wish. This is in contrast to iOS where views navigate in and out full screen, meaning there can be some jumping around to get to different information. My feeling is the extra real estate that the Vision Pro affords lends itself to this approach, rather than being limited to a single window. It also means I can rely on the in-built window management.</p><p>Now I&#8217;m not expecting everyone, or even most people will want this kind of detail, but I&#8217;m hoping those that do appreciate the flexibility the app affords.</p><p>Apple&#8217;s Human Interface Guidelines actually <a href="https://developer.apple.com/design/human-interface-guidelines/spatial-layout#Best-practices">warn against this kind of behaviour</a> stating:</p><div class="pullquote"><p> &#8220;Too many windows can obscure people&#8217;s surroundings, making them feel overwhelmed, constricted, and even uncomfortable&#8221; - Apple HIG</p></div><p>I&#8217;m hoping with the specific use case of this app, that best practice can be stretched somewhat. It&#8217;s still possible to get the full experience of the app with only one open window, but for &#8220;power users&#8221; it&#8217;s possible to get more from the app. I&#8217;ll be interesting to see how my approach works in practice as I feel these windows aren&#8217;t intrusive and all provide value.</p><p>Hopefully this aspect of the User Experience is something that will standardise over time, once developers show their approaches and the platform matures beyond the initial release.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! Subscribe for free to receive new posts.</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[Have we reached Peak App?]]></title><description><![CDATA[Is the concept of the App about to change forever?]]></description><link>https://varrall.substack.com/p/peak-app</link><guid isPermaLink="false">https://varrall.substack.com/p/peak-app</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Sat, 23 Sep 2023 02:02:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!K_Lp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg" 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_!K_Lp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K_Lp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 424w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 848w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K_Lp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg" width="1024" height="543" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:543,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80070,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&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="" srcset="https://substackcdn.com/image/fetch/$s_!K_Lp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 424w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 848w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!K_Lp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a821ea-2786-4063-8a1e-bcc9fd383427_1024x543.jpeg 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>I&#8217;ve been pondering an idea for the last few months, since WWDC, which is whether we have passed Peak App? Has the idea of what we think an App is reached a crest and are we currently experiencing the beginning of a new era for the humbleApp? What I mean by Peak App, isn&#8217;t that Apps are doomed, but more what we think of as an App is changing for ever, and that we&#8217;re seeing in a new phase of their evolution with the explosion of platforms and widgets.</p><p>The way in which we experience Apps is no-longer hidden behind an icon, a simple full screen, pixel perfect, single process view of their data. For the last few years, focus has been on Apps supporting more platforms, iPhone, iPad, Apple Watch, Mac, Vision Pro, mostly powered by SwiftUI&#8217;s rise.&nbsp; Additionally Apps can support Widgets, which are now interactive, Siri Intents, Live Activities, Share Clips, Shortcuts, Notifications and with iPhone 15 the Action Button. A myriad of ways for users to interact with an App without actually opening it. Jason Snell had a similar insight on the <a href="https://www.relay.fm/upgrade/477">Upgrade podcast, episode 477</a>&nbsp; where he said:</p><blockquote><p>&#8220;we&#8217;re potentially entering an era where Apple is sort of saying, there are lots of interactions you do with apps that you don&#8217;t have to do in the app&#8230;. That&#8217;s an interesting shift in philosophy to say opening the apps is not the way you have to interact with them, in fact some apps I would imagine, it be very rare that you need to go in&#8221;. <a href="https://mastodon.social/@jsnell@zeppelin.flights">Jason Snell</a></p></blockquote><p>More and more we&#8217;ll see the core functionality of an app accessed through system interactions, offered by the app, but presented by processes outside of the Apps control. In the future, I can see apps being more like APIs, exposing their data through endpoints (intents), allowing users to indirectly interact with the app in even more places. It will be possible to combine functionality between different apps, joining the output of one intent with another or presenting them together in new ways. We see this already with Shortcuts, and somewhat with Interactive Widgets, but this ability will only get more powerful and easy for the user to control going forwards. </p><div class="pullquote"><p>Will we eventually see a Widget Store, the ability to get the functionality of a widget, without first downloading an App?</p></div><p>Apple often foreshadows their plans with features that may look underwhelming in themselves, but form part of a grander vision. There&#8217;s been a push for Intents for years, first donating them to the system, then with Shortcut integration and now with Interactive widgets. Widgets are everywhere, Apple has been slowly making them more powerful and more useful across home screens, lock screens, StandBy and watch faces. With the inevitability of an LLM based Siri coming, how else will that work for App integration, if not for an Intent based system with results visually displayed on the best device to you, the phone in your hand or on it&#8217;s stand, the TV on the wall, or directly into your world through the Vision Pro.</p><p>I can see Apps no longer being delivered via a coherent interface, but a set of features that can be mixed and matched together, the experience would be widget like, just a lot more powerful and not restricted to the Home Screen. Drop a weather widget into your sports app, combine a time tracking widget next to your event in the calendar app. The control will be with the user in how they interact with your features, not the developer. Why limit the use of a widget to the confines of a screen designed for App Icons? It&#8217;s clear the current Home Screen is too limiting for Widgets as they mature, but will Apple go so far as to remove icons themselves from the Home Screen altogether? It doesn&#8217;t seem out of the question, a small widget offering live information is way more powerful than a static icon. Could it lead to a Widget Store? A future where functionality and quick interactions supersedes complexity and breadth of features?  </p><div class="pullquote"><p>What we currently know as the Home Screen is too limiting for Widgets as they mature</p></div><p>In Federico Viticci&#8217;s excellent <a href="https://www.macstories.net/stories/ios-and-ipados-17-the-macstories-review/16/#content">iOS and iPadOS 17 review </a> on MacStories he concludes with the same sentiment;</p><blockquote><p>A modern app for the Apple ecosystem is more than an icon on the Home Screen: it&#8217;s a <em>container</em> for a diverse set of experiences on a range of different hardware products&#8230;. What does it even mean to be an app anymore? - <a href="https://mastodon.social/@viticci@macstories.net">Federico Viticci</a></p></blockquote><p>What does this mean for developers? I would recommend leaning into App Intents, integrating interactive widgets, App Shortcuts and developing your apps to be as modular as possible. Plan for all features to be accessed independently and if you haven&#8217;t already then lean into SwiftUI and native development. If you&#8217;re reliant on cross platform tools, you&#8217;ll be more and more ostracised from this journey.</p><p>If the last 15 years is anything to go by, then just as you think things have reach their peak and will settle down, there&#8217;s another paradigm waiting just over the horizon. It seems like we are currently experiencing the beginnings of this again, and once more things will change forever.</p><p>The App is dead. Long live the App.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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! 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 Charts]]></title><description><![CDATA[I should have done it sooner]]></description><link>https://varrall.substack.com/p/swift-charts</link><guid isPermaLink="false">https://varrall.substack.com/p/swift-charts</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Tue, 22 Aug 2023 12:33:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apple released their Swift Charts library back in 2022 as a native way to generate a whole host of different chart types without having to reach for an external library, or do what I did, and roll your own. The benefits should be clear as to why to use the inbuilt approach, but accessibility and simplicity were top of my list and the negatives mainly down to lacking features (no pie chart!?).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lZad!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lZad!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 424w, https://substackcdn.com/image/fetch/$s_!lZad!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 848w, https://substackcdn.com/image/fetch/$s_!lZad!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 1272w, https://substackcdn.com/image/fetch/$s_!lZad!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lZad!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png" width="1456" height="530" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:530,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:124551,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lZad!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 424w, https://substackcdn.com/image/fetch/$s_!lZad!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 848w, https://substackcdn.com/image/fetch/$s_!lZad!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.png 1272w, https://substackcdn.com/image/fetch/$s_!lZad!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f7a113b-6993-4901-8851-e919b4ac222a_1488x542.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">Swift Chart Samples by Apple</figcaption></figure></div><p>The Cricket App I&#8217;ve been building has been a slow burn, initially built at a time long before Swift Charts, and given that I&#8217;d been coding it exclusively on Swift Playgrounds, support for Charts was even longer coming. So by the time it was available, I had a chart I was happy with and couldn&#8217;t see a good reason to change.  Until now.</p><p>With the introduction of the Vision Pro, I thought now was a good time (excuse) to transition across to benefit from better native support for things like interactions and view resizing. Having allocated an evening to get things up and running, I was pleased to discover that with only a few lines of code, I was able to get a chart up and running. With a bit more playing with styling, particularly with axis, I could a near replica of the original chart (left) with only 10% of the original code. There&#8217;s a few visual changes, mainly to accommodate the chart on the Vision Pro at this point.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UJZR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UJZR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 424w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 848w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 1272w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UJZR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png" width="1456" height="663" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:663,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:831628,&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;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UJZR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 424w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 848w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 1272w, https://substackcdn.com/image/fetch/$s_!UJZR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff840425a-0511-4c80-950a-f0489cb3d085_2964x1349.png 1456w" sizes="100vw"></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">LEFT: Original custom Chart ~500 lines | RIGHT: Swift Charts ~50 lines</figcaption></figure></div><p>Going forwards this new approach will allow much quicker iterations on how the chart looks and feels and hopefully lets me explore some new options with other data that can be charted in other areas of the app without having to code a completely new custom chart.</p>]]></content:encoded></item><item><title><![CDATA[Adapting an App to visionOS]]></title><description><![CDATA[Documenting the initial experience]]></description><link>https://varrall.substack.com/p/adapting-an-app-to-visionos</link><guid isPermaLink="false">https://varrall.substack.com/p/adapting-an-app-to-visionos</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Mon, 07 Aug 2023 07:31:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4heZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve been continuing to explore Vision Pro, this time with the intention of bringing my Cricket App to the device. The App is built in SwiftUI and uses almost exclusively built in components. It also relies heavily on Apple&#8217;s design principles, with support for light and dark modes, so in theory and in practice it was easy to get running on the Vision Pro Simulator with little requiring changes. It would have been even easier, but the iOS version was written exclusively on the iPad using Swift Playgrounds, which understandably has no support for visionOS. My solution was to create a brand new visionOS project in Xcode and copy the files across from the Playground package. Not a seamless experience, and they may be a better way, but it gave me a clean start. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4heZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4heZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 424w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 848w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4heZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png" width="1456" height="546" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:546,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12142966,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4heZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 424w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 848w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!4heZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb369a9c9-4fe0-451e-a390-a7970a50cd09_5464x2048.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">Initial version of the App on the left, with the updated version with some Vision Pro accommodates on the right.</figcaption></figure></div><p>Getting it running was one thing, but being a good platform citizen and fitting in seamlessly is another. </p><p>The first thing to tackle was the app had no windowing support, so creating an WindowGroup and allowing an individual game&#8217;s content to be opened separately was the first step. Getting this working was straightforward, but extracting out some assumptions of the data model to accomodate this new concept took a little more time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jw-Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png" width="1456" height="1091" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1091,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4990980,&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;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!Jw-Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F151bf010-cfb9-4415-b9ba-3a96b222a3a5_2732x2048.png 1456w" sizes="100vw"></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>Once the practical steps were out the way, I could turn my attention to making it a bit more compelling of an experience. Adopting ornaments was the first step, adding in a Tab Bar in order to benefit from the rather handy navigation it enables was a natural addition. Whilst playing with ornaments I also tried adding the window title as a top ornament, which I liked the look of more than what I&#8217;ve seen as standard, which is a large title in the top left of a window. I&#8217;m not completely sold and could drop the idea, but I feel it frees up the content and is more obvious than the subtle tab bar icons to differentiate the current selection. This change also forced me to adopt a grid view rather than a list view for the upcoming games. On visionOS the windows are a default size, so long lists don&#8217;t work as well as a grid of content.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!khE8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!khE8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 424w, https://substackcdn.com/image/fetch/$s_!khE8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 848w, https://substackcdn.com/image/fetch/$s_!khE8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 1272w, https://substackcdn.com/image/fetch/$s_!khE8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!khE8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png" width="1142" height="748" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:748,&quot;width&quot;:1142,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:873062,&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;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!khE8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 424w, https://substackcdn.com/image/fetch/$s_!khE8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 848w, https://substackcdn.com/image/fetch/$s_!khE8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 1272w, https://substackcdn.com/image/fetch/$s_!khE8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54444108-d39d-4544-a5a9-b65b432990ed_1142x748.png 1456w" sizes="100vw"></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">Ornaments in action</figcaption></figure></div><p>I did fall foul of not providing the ornament to the correct view. It only worked when the ornament was added to the main Tab Bar View, not each individual Tab&#8217;s View. I was caught here as if added to the Tab&#8217;s View it does show, but not if you change tabs and not always. I&#8217;m unsure if this is a bug, or user error, but either way it&#8217;s something to look out for.</p><p>Next was to clean up some of the backgrounds and colours used. One of the first things I noticed was that coloured text just doesn&#8217;t work on visionOS. The glass background makes the addition of coloured text very hard to read. There might be exceptions, I would assume this is something that all apps will need to look out for. It is something called out in an Apple Design Video, with their suggesting to add solid colours as a background, but in my situation where the content isn&#8217;t a button, this wasn&#8217;t an option.</p><p>There were also a few places, where I&#8217;d used fixed colours, such as the chart background as well as .secondary and .tertiary as background fills as well as layered different blur effects in places. These didn&#8217;t work on the Vision Pro, where care needs to be taken to alternate light and dark background views, or at minimum not have lighter on light views. I&#8217;m still not happy with where this ended up, but it was already a vast improvement over the initial version.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LBCe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LBCe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 424w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 848w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 1272w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LBCe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png" width="800" height="265" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2258b04d-9715-459a-a218-d35041067a35_800x265.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:265,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:133488,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LBCe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 424w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 848w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.png 1272w, https://substackcdn.com/image/fetch/$s_!LBCe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2258b04d-9715-459a-a218-d35041067a35_800x265.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">Before and after of the cell used to show fixture overview. The left has coloured text and backgrounds, replaced with white text and glass effects on the right</figcaption></figure></div><p>One area I&#8217;ve found hard to understand is sizing and spacing of content. Everything just looks so small and compact on the simulator. I&#8217;m aware that windows viewed on device, will be larger and clearer than trying to view on a window on my Mac, but it&#8217;s hard to fight the temptation just to double the size of everything to accommodate. Which reminds me, I haven&#8217;t yet optimised the layout for accessibility settings, which there are more of than ever on visionOS. I&#8217;m confident that adopting the layout to a variety of options such as dynamic type and reduced transparency will help refine the usability across the board.</p><p>This is how I anticipate the app being used, in conjunction with live content, giving the viewer additional context about the ongoing game.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QXE8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QXE8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QXE8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png" width="1456" height="1091" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1091,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4795305,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QXE8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 424w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 848w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!QXE8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0a6631-fc81-424b-b490-335664d3f130_2732x2048.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 Cricket app in a window whist watching the game live on the big screen</figcaption></figure></div><p>All in all, for only a few hours work, I&#8217;m really happy with how much progress I was able to make. There&#8217;s lots still to improve and iterate on, but considering this is a whole new platform, the transition isn&#8217;t as dramatic as it could have been. If you have any suggestions how I might further improve things please let me know.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Windowing on the Vision Pro]]></title><description><![CDATA[Now where did I leave that settings panel....]]></description><link>https://varrall.substack.com/p/windowing-on-the-vision-pro</link><guid isPermaLink="false">https://varrall.substack.com/p/windowing-on-the-vision-pro</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Mon, 24 Jul 2023 07:00:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d82f4c2e-acc0-4573-ad65-12e74cd33ee5_1444x726.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Window management in visionOS is handled using familiar SwiftUI APIs, but with a few restrictions as well as opening up some new opportunities. </p><p>First thing&#8217;s first, if you&#8217;re extending an existing App, then you may need to <a href="https://developer.apple.com/documentation/visionos/presenting-windows-and-spaces/#Enable-multiple-simultaneous-scenes">Enable Multiple Windows</a> support in the info.plist of your app. Adding the `UIApplicationSupportsMultipleScenes` with a value of true will unlock support (required on visionOS and iPadOS. Then it&#8217;s as simple as calling openWindow by specifying the id of your window group using the <a href="https://developer.apple.com/documentation/SwiftUI/EnvironmentValues/openWindow">openWindow</a> environment action.</p><p>Once you&#8217;ve done that, there&#8217;s seemingly no limit to the number of windows your app can open or where your users can put them.</p><pre><code>struct MainWindowView: View {
    @Environment(\.openWindow) private var openWindow
    var body: some View {
        VStack {
            Text("Main Window").font(.title)
            Button("Open") {
                openWindow(id: "childWindow")
            }
        }
    }
}</code></pre><blockquote><p>Apple changed the way windowing works on Beta 2, allowing more control of window sizing. The below has been updated to reflect that.</p></blockquote><p>In Apple&#8217;s attempt to control the user experience, and ensure the user isn&#8217;t disorientated, by default every window launched is the same size and front and centre of the user&#8217;s view. Initially you couldn&#8217;t override this functionality, but as of Beta 2 it is possible to add a `defaultSize` modifier to set the initial size of the window, but still not its position. It also doesn&#8217;t seem possible to adjust this programatically after the fact (without some very low level, and clearly unintended, trickery)</p><pre><code>.defaultSize(width: 0.5, height: 0.75, depth: 0.0, in: .meters)</code></pre><p>If you do need a specific and fixed size for your content, then one trick is to use a <a href="https://developer.apple.com/documentation/swiftui/windowstyle/volumetric/">volumetric window</a>, which does allow you to set a size. Volumetric windows are still opened in the middle of the user&#8217;s eyeliner, but can be moved around and placed, but not ever resized by the user.</p><p>Points in visionOS are calculated differently to other platforms, and don&#8217;t represent a fixed number of pixels. Instead a point on visionOS is an angle, which means content can dynamically resize as it moves closer or further away from you, in order to maintain legible content. The <a href="https://developer.apple.com/design/human-interface-guidelines/spatial-layout#Scale">recommendation is</a> to only fix the size of an object if it represents a real world object, of a specific size, otherwise it&#8217;s best to let the user control how they interact with your content.</p><p>The following code allows you to create a window with a fixed size, which can&#8217;t be controlled by the user. Despite this being a Volumetric window, it can still contain only &#8216;UIKit&#8217; elements, the only difference seemingly is no-matter what the depth of the view is set to, it&#8217;s possible to move the window around from the &#8216;side&#8217; which also re-orientates the view perpendicular to the user.</p><pre><code>WindowGroup(id: "main") {
    MainWindow()
}
.windowStyle(.volumetric)
.defaultSize(width: 1, height: 1, depth: 0.1, in: .meters)</code></pre><p>There&#8217;s no concept of a Window in visionOS, only WindowGroup. That means a user can open multiple copies of a window by default. This may be intentional, as I imagine it could get easy to lose windows in your environment, so having the ability to create them at will means the user can control this and even leave multiple copies of windows around their environment.</p><p>If you want to ensure only one copy of your window group, then the easiest way to manage that, is to dismiss the WindowGroup right before you open it. This will mean that it re-opens front and centre where the user is looking. This could of course be a benefit, in case they&#8217;ve misplaced the window somewhere.</p><pre><code>Button("open") {
    dismissWindow(id: "targetView")
    openWindow(id: "targetView")
}</code></pre><p>If you don&#8217;t want windows jumping around, then you&#8217;ll need to keep track of which views are open and dismissed using a combination of Scene Phases and/or `onAppear`, `onDisappear` modifiers to manually control the ability for the user to open a new instance of that WindowGroup. Scene Phase doesn&#8217;t report when a window is dismissed, either using dismissWindow or using the visionOS close button that&#8217;s attached to every window. It also doesn&#8217;t support the `controlActiveState` environment variable, which is a a macOS solution to get this information.</p><pre><code>WindowGroup("Target View", id: "targetView") {
    TargetView()
        .onAppear {
            print("target window appeared")
        }
        .onDisappear {
            print("target window disappeared")
        }
}

struct TargetView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        Text("Target View")
            .onChange(of: scenePhase, { _, phase in
                print("scene changed: \(phase)")
            })
    }
    
}</code></pre><div class="pullquote"><p>There seems to be a missing &#8216;find my window&#8217; feature of visionOS at present, with no Mission Control or Expos&#233; feature. Using the Digital Crown to recenter content may disrupt someone&#8217;s carefully constructed &#8220;desktop&#8221; too much.</p><p>I wonder if you will need be able to &#8216;ping&#8217; windows or apps to play a sound to hunt them down or even the need to guide the user to a window with a directional experience similar to tracking down an AirTag.</p></div><h2>Immersion</h2><p>If you need more than just windows then the Immersive space is for you. Here you get ultimate control of where objects are placed and aren&#8217;t restricted to a window at all (although windows still work here too). Even if you do have an app that suits an immersive experience, it&#8217;s still recommended to launch into a regular window to start with, using say a button within that window to open an immersive space under the control of the user. Perhaps if you have a true immersive experience or game, then after the first time, subsequent launches can go straight to the action if appropriate. To open directly into an immersive space requires setting another plist value, this time `<a href="https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationpreferreddefaultscenesessionrole">UIApplicationPreferredDefaultSceneSessionRole</a>` needs a value of `UISceneSessionRoleImmersiveSpaceApplication`.  </p><blockquote><p>Note: It doesn&#8217;t currently seem possible to open an immersive space upon app launch in simulator. You do need to specify a key in the info.plist to allow this in the first place, but in my experience, <s>the immersive view isn&#8217;t rendered</s>. In Beta 2 it seems this key is ignored and the app refuses to launch.</p></blockquote><p>It&#8217;s worth knowing, that currently the original window group remains open when opening an immersive space. This may not be desirable for your experience, it certainly feels a bit redundant in some circumstances, even if the user can always manually close this window. This does reveal a weird UX issue though, as if the user closes the initial window, then also closes the immersive space, then your app may have no windows left, so it will be fully closed.</p><p>If you don&#8217;t want this behaviour then you can resort to managing the opening and closing window groups manually. Apple handles this weirdly in the Earth sample app, by using opacity to hide the main view, replacing it with a smaller, space specific modal. I&#8217;m not a fan of this approach as if you move that view out the way, the main view is confusingly (to the user) also moved. From a design perspective, if the new view looks and acts like a different window, it should be treated as such. My preference is to open a separate window group, or use the attachments option of the RealityView to position the modal view within the scene itself.</p><pre><code><strong>In the main window, open the immersive space as normal:
</strong>await openImmersiveSpace(id: "ImmersiveSpace")

<strong>When the immersive space opens, dismiss the original window:</strong>
.onAppear {
    dismissWindow(id: "mainView")
}<strong>

When you want to exit out of the immersive space:</strong>

//re-open the main window
openWindow(id: "mainView")

//if we don't wait here, the app doesn't seem to have time to open the window
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    Task {
        //then dismiss the immersive space
        await dismissImmersiveSpace()
    }
}</code></pre><h2>A window into the future</h2><p>As developers explore the options with windows, then I expect certain paradigms to become the standard, perhaps with some changes to visionOS as the beta period continues. <s>One such limitation with visionOS Beta1, is that there is no access to the Digital Crown, which means there&#8217;s no way to test the progressive immersive space, in which the user controls the amount of immersion of the space, even going from .mixed through to the .full display options as they desire</s>. This has now been resolved as of visionOS 1.0 Beta 2, it&#8217;s now possible to set the immersion values to 30, 50 or 100% via the I/O menu.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!62cX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!62cX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 424w, https://substackcdn.com/image/fetch/$s_!62cX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 848w, https://substackcdn.com/image/fetch/$s_!62cX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 1272w, https://substackcdn.com/image/fetch/$s_!62cX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!62cX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png" width="516" height="176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:176,&quot;width&quot;:516,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86261,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!62cX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 424w, https://substackcdn.com/image/fetch/$s_!62cX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 848w, https://substackcdn.com/image/fetch/$s_!62cX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 1272w, https://substackcdn.com/image/fetch/$s_!62cX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feeab3f35-5a02-4ebf-87e0-8ea9c49535e0_516x176.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Simulate the Digital Crown using specific immersion values via a menu</figcaption></figure></div><p>I also haven&#8217;t touched upon opening windows onto surfaces, re-orientating windows so they&#8217;re flat on a floor/table, or portals. I feel like a part 2 is coming&#8230;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Hand tracking in VisionOS]]></title><description><![CDATA[Exposing Skeletons, Chirality and Joints]]></description><link>https://varrall.substack.com/p/hand-tracking-in-visionos</link><guid isPermaLink="false">https://varrall.substack.com/p/hand-tracking-in-visionos</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Wed, 19 Jul 2023 01:49:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!krMV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Updated for visionOS 1.0 beta 2</p></blockquote><p>Hand Tracking is a key feature of the Vision Pro, enabling the main user interaction with the device. It allows the device to keep track of a user&#8217;s hand position, rotation and size (transform) in the scene as well as each joint within the hand.  With permission, it&#8217;s possible for an app to access this data in order to power their own types of features, which is what we&#8217;ll dive into in this article.</p><p>The data we as App developers have access to is provided under an <a href="https://developer.apple.com/documentation/arkit/handanchor">HandAnchor</a> entity. This is a special type of Anchor that contains a number of important princes of information. <a href="https://en.wikipedia.org/wiki/Chirality">Chirality</a> tells us whether this is the left or right hand. It contains a  <a href="https://developer.apple.com/documentation/arkit/handanchor/4284943-handskeleton">hand skeleton</a>, revealing details about the joints in the hand. It also consists of the base transform of the hand in world space.</p><p>In the WWDC session, &#8220;<a href="https://developer.apple.com/videos/play/wwdc2023/10082/?time=933">Meet ARKit for spatial computing</a>&#8221; we get (the only?) insight into what this skeleton is:</p><blockquote><p>note: in visionOS beta 2, the &#8220;hand&#8221; prefix has been removed from these references</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!krMV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!krMV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 424w, https://substackcdn.com/image/fetch/$s_!krMV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 848w, https://substackcdn.com/image/fetch/$s_!krMV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 1272w, https://substackcdn.com/image/fetch/$s_!krMV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!krMV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png" width="1456" height="834" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:834,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1125649,&quot;alt&quot;:&quot;&quot;,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!krMV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 424w, https://substackcdn.com/image/fetch/$s_!krMV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 848w, https://substackcdn.com/image/fetch/$s_!krMV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.png 1272w, https://substackcdn.com/image/fetch/$s_!krMV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b47d7f-6110-49a3-b955-9b4632cb0210_1644x942.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>Each joint is attached to a parent joint, with the .wrist being the root. The `wrist` being the root is the only gotcha with this hierarchy. All of the fingers, the thumb, but also the forearm flow back here. This means the parent of `forearmArm` is `forearmWrist`, whose parent is `wrist`, which is a bit counterintuitive</p><p> For those anatomists among us, you may be confused with the splitting of the metacarpal and the knuckle, as each refers to the same thing. Here it seems Apple is referring to the metacarpal bone and the knuckle joint. It&#8217;s a bit different to the references used for the Vision (2D) version of <a href="https://developer.apple.com/wwdc20/10653?time=345">Hand Tracking</a> which uses the actual joint names to reference the positions. These can be mapped as below:</p><div class="preformatted-block" data-component-name="PreformattedTextBlockToDOM"><label class="hide-text" contenteditable="false">Text within this block will maintain its original spacing when published</label><pre class="text"><strong>VisionOS</strong>                                                   <strong>Vision</strong>
<em>(SkeletonDefinition.JointName)</em>&#9;            <em>(VNHumanHandPoseObservation.JointName)</em>
.indexFingerTip&#9;                            .indexTip
.indexFingerIntermediateTip&#9;    .indexDIP
.indexFingerIntermediateBase       .indexPIP
.indexFingerKnuckle&#9;                    .indexMCP
.indexFingerMetacarpal&#9;            No equivalent
.thumbTip&#9;                                    .thumbTip
.thumbIntermediateTip&#9;            .thumbIP
.thumbIntermediateBase&#9;            .thumbMP
.thumbKnuckle&#9;                            .thumbCMC
.wrist&#9;                                            .wrist
.forearmWrist&#9;                            No equivalent
.forearmArm&#9;                                    No equivalent</pre></div><p>Each of the fingers follow the same pattern as above, with &#8216;index&#8217; replaced by, &#8216;middle&#8217;, &#8216;ring&#8217; and &#8216;little&#8217; as appropriate. It&#8217;s certainly a bit confusing, to go from anatomically correct names, to made up landmarks, with some mixed up reference to anatomy.</p><h2>Tracking a Hand</h2><p>How do we go about actually tracking a hand? Well first caveat, is without a device, you can&#8217;t! Hand tracking isn&#8217;t available in the simulator and there&#8217;s no other device that exposes the HandAnchor for us to play with, so this is all theoretical for now. Regardless, if you want to prepare your app for when it is possible, there are a few things to be aware of. First off, you need an ARKitSession, then you need the user&#8217;s permission to access that session. This comes in the form of a .handTracking request via ARKit. Note, unlike permission prompts on other platforms, this is an option request, as ARKit will automatically prompt the user if you leave this out, rather than crash outright. However, without type of check, you won&#8217;t know if someone has denied your request, and be able to handle it appropriately.</p><pre><code>let session = ARKitSession()
func requestAuth() async {
        let request = await session.requestAuthorization(for: [.handTracking])
        for (authorizationType, authorizationStatus) in request {
            //handle auth here
        }
    }</code></pre><p>Once we have permission, we can add hand tracking to the session, using a HandTrackingProvider, then retrieve any updates asynchronously to the hand anchors as they are made available.</p><pre><code>class HandTracking {
    let session = ARKitSession()
    let handTracking = HandTrackingProvider()
    
    func runSession() async {
        do {
            try await session.run([handTracking])
        } catch {
            print("error starting session: \(error)")
        }
    }
    func processHandUpdates() async {
        for await update in handTracking.anchorUpdates {
            let handAnchor = update.anchor
            
            guard handAnchor.isTracked else { continue }
            
            //do something with hand/joints
        }
    }
}</code></pre><h2>Limitations</h2><p>We&#8217;ve already come across one limitation of hand tracking, in that it&#8217;s not available in the simulator at present. The second limitation is your app has to be running in an independent full space, not in the shared space with other apps. This means your app needs to be the only one in focus in order to access any hand tracking data (as well as other types of ARKit scene data). If you had ideas about a fully gesture based Todo app, or say an always open music player that you could control with gestures, whilst working in other apps, then that won&#8217;t be possible. It makes sense, as you could easily have multiple apps with conflicting gestures, as well it being a real drain on system resources.</p><h2>What is Possible?</h2><p>So what can you do with hand data? Well custom gestures is one, as shown in the <a href="https://developer.apple.com/documentation/visionos/happybeam/">Happy Beam sample code </a>provided by Apple. It&#8217;s a bit convoluted right now, to extract each joint position, calculate distances between them and use that to determine when a gesture has been made, but it is possible. An extension of this is direct interaction with objects in the spatial world. Picking up objects or applying forces are possible by <a href="https://developer.apple.com/wwdc23/10082?time=1404">attaching physics objects</a> directly to your hands.  </p><p>Finally attaching 3D models or 2D overlays to the hand opens up other possibilities, particularly for games.  Who wouldn&#8217;t want a Tony Stark style Power Gauntlet to control? </p><p>It will be interesting to see what Apple has planned for developers to access these features, without have a Vision Pro to test with, as it seems like it will be difficult otherwise.  In the meantime, if you want to simulate what it could be like, then see my other article about <a href="https://varrall.substack.com/p/hand-tracking-in-vision-pro-simulator">faking hand tracking</a> in the VisionOS Simulator&#8230;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Skyboxes and transparency in VisionOS]]></title><description><![CDATA[Experiments in Double sided materials]]></description><link>https://varrall.substack.com/p/skyboxes-and-transparency-in-visionos</link><guid isPermaLink="false">https://varrall.substack.com/p/skyboxes-and-transparency-in-visionos</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Mon, 17 Jul 2023 07:43:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!U9Lw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was recently experimenting with adding a glowing gradient material to a cylinder in RealityKit for use on the Vision Pro. This led me down a journey that crossed meshes, materials, Blender, Reality Composer Pro and a bug in Apple&#8217;s rendering.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U9Lw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U9Lw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 424w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 848w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U9Lw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png" width="894" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2fad00d9-588a-49ed-a618-54139b815825_894x768.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:894,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:982139,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U9Lw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 424w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 848w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Lw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fad00d9-588a-49ed-a618-54139b815825_894x768.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">Orange cylinder with double sided rendering, next to a blue cylinder with only front faces being rendered</figcaption></figure></div><p>The first stumbling block was having no cylinder mesh resource in RealityKit, like there is in SceneKit. For &#8220;simple&#8221; things I prefer to generate meshes and materials in code where possible. It keeps app sizes down, and keeps the asset workflow simpler. </p><p>However in this case, it was necessary to rely on Reality Composer Pro, which does have a Cylinder geometry and opened up the potential to explore the shader graph feature for the first time.</p><p>With a bit of research and some trial and error I was able to create a nice pixel shader effect that uses the height of the pixel to determine the transparency, along with an added remap node to ensure the bottom of the cylinder (&lt; 0.2 in y) is also transparent. With a bit of experimenting and reversing the gradient with a -1 constant multiplication for the y position, local to the mesh I had the gradient &#8220;glow&#8221; I was looking for.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jx98!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jx98!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 424w, https://substackcdn.com/image/fetch/$s_!jx98!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 848w, https://substackcdn.com/image/fetch/$s_!jx98!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 1272w, https://substackcdn.com/image/fetch/$s_!jx98!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jx98!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png" width="1456" height="275" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:275,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:232714,&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;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jx98!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 424w, https://substackcdn.com/image/fetch/$s_!jx98!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 848w, https://substackcdn.com/image/fetch/$s_!jx98!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 1272w, https://substackcdn.com/image/fetch/$s_!jx98!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58472f0c-c749-4ad7-8053-dab69bc015ce_2298x434.png 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption">Reality Composer Pro Shader Graph with a gradient, transparent material</figcaption></figure></div><p>This was the effect I was after, but I couldn&#8217;t uncover a way to make the material double sided. I couldn&#8217;t see any options for the unlit surface I thought was appropriate, nor, in my desperation figure out a vertex shader that would achieve the effect.</p><p>Almost simultaneously there was <a href="https://mastodon.social/@branfonk/110697192684220614">a discussion on Mastodon</a> about the lack of an out-the-box skybox for VisionOS. In Apple&#8217;s sample code, they use a trick of negative scaling the entity to create a skybox. This is certainly one solution, but isn&#8217;t possible in Reality Composer Pro as negative scales aren&#8217;t possible.  This means you can only visualise the result within Xcode (either the preview or Simulator).</p><pre><code>//scale a mesh entity to reverse the surface rendering
.scale *= .init(x: -1, y: 1, z: 1)</code></pre><p>It&#8217;s also not a solution for my situation as I needed a double sided material, not just an inverted one. One way around this is to duplicate the entity and only invert one, again within code. This does solve the problem, but I wasn&#8217;t satisfied with it as a solution, as it just feels wasteful and relies on a runtime fix. Online I found a few other options, one being to duplicate just the mesh, rewriting the vertices in reverse order. This certainly inverts the face rendering, but without testing, my gut feel this would be more expensive than duplicating the entity itself.</p><p>Going back to experimenting, I discovered that my original choice of material, &#8216;unlit&#8217; may have been hampering my options. Two other materials, &#8216;<a href="https://developer.apple.com/documentation/realitykit/custommaterial">Custom</a>&#8217; and &#8216;<a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial">PhysicallyBasedMaterial</a>&#8217; have a &#8216;<a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial/faceculling-swift.property">faceCulling</a>&#8217; option, which is described by Apple as:</p><blockquote><p>To improve performance, RealityKit culls polygons, or <em>faces</em>, that it determines won&#8217;t be visible. Discarding faces that aren&#8217;t part of the final render eliminates the need to do any calculations for those faces.</p></blockquote><p>The three options for culling are &#8216;back&#8217; (default), &#8216;front&#8217;, or &#8216;none&#8217;. Bingo, this is exactly what we needed. This serves the purpose for both what I needed (.none) and the requirement for a skybox (.front), without needing to scale the entity.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2Dkp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2Dkp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 424w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 848w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 1272w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2Dkp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png" width="1428" height="636" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:636,&quot;width&quot;:1428,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1333214,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2Dkp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 424w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 848w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.png 1272w, https://substackcdn.com/image/fetch/$s_!2Dkp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3252f74-adb4-4180-b085-cff539d54bc6_1428x636.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">3 Spheres with a transparent checkerboard texture. A Green sphere with culling .none, so you can see both inside and outside. A purple sphere with culling .front, so the inside of the sphere is visible. A red sphere, with .back showing a usual view. </figcaption></figure></div><p>There are two issues with this approach. There&#8217;s added complexity (and potential performance implications) of these shaders over the &#8216;unlit&#8217; I&#8217;d been using. My assumption would be the simplicity of unlit, allows for some performance gains under the hood over a custom or physics based shader. This could certainly do with some additional investigation, but is a small problem compared to the additional issue of CustomMaterial&#8217;s weirdly not yet being available for VisionOS, so aren&#8217;t even an option here if shaders are part of the rendering. It&#8217;s still a great solution for a Skybox situation, using a PhysicallyBasedMaterial, but isn&#8217;t possible yet using a Reality Composer Pro workflow (no access to any parameters for a `ShaderGraphMaterial`).</p><p>One thing to note, is there&#8217;s an issue (FB12605220) with RealityKit when using blending and no face culling, that causes some erroneous culling still as shown below in the left image.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xtvk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xtvk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 424w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 848w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 1272w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xtvk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png" width="1456" height="361" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:361,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2976605,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xtvk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 424w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 848w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 1272w, https://substackcdn.com/image/fetch/$s_!xtvk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde0e4022-91b0-4f9a-b6b8-8bfc441d1362_3836x950.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">A cube with a transparent checkerboard texture rendered with erroneous culling on the left, whilst rendering correctly on the right once .opacityThreshold is set</figcaption></figure></div><p>A fix is to specify a value for &#8216;opacityThreshold&#8217; on the material, even if it&#8217;s just the default of 0.0. This seems to kick the rendering into gear and it then correctly renders the transparency with no-culling of &#8220;hidden&#8221; faces. </p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Hand Tracking in Vision Pro Simulator]]></title><description><![CDATA[Fake it until you make it...]]></description><link>https://varrall.substack.com/p/hand-tracking-in-vision-pro-simulator</link><guid isPermaLink="false">https://varrall.substack.com/p/hand-tracking-in-vision-pro-simulator</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Fri, 14 Jul 2023 02:22:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bkUU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a775922b-0977-4632-936c-54878f6c3fdf&quot;,&quot;duration&quot;:null}"></div><p>There&#8217;s currently (as of Xcode 15 beta3 VisionOS SDK 1.0) no way of testing or simulating hand tracking in the Vision Pro Simulator. In fact, many of the <a href="https://developer.apple.com/documentation/arkit/arkit_in_visionos">Recognizers</a>, the bridges between the real world and our apps, that will unlock many of the engaging Spatial experiences don&#8217;t (yet?) exist in the Simulator.</p><p>To combat this I have been experimenting with using Playgrounds on the iPad to get a feel of how scene, plane, hand and body tracking would work. This works really well for prototyping and is a super responsive workflow. Changes can been seen &#8220;instantly&#8221; in the preview and the ability to take the iPad anywhere (and make changes from anywhere) is important for AR. This workflow falls short with replicating the experience of running an app on the Vision Pro as Playgrounds doesn&#8217;t have the Vision SDK (understandable, it&#8217;s unlikely to), or even the latest iPadOS 17 features. (I&#8217;m hoping for an updated Playgrounds app soon).</p><div><hr></div><h2>Experimenting</h2><p>I was experimenting with running a customML object detection model on a video playing within the Vision Pro. It happened that code snippet I found for extracting the frame buffer included a CIFilter step. That got me thinking about hand tracking, and if I could bring a form of hand tracking in the Simulator.</p><p>Thanks to same Apple sample code and a the work I&#8217;d already done on the iPad with VNHumanHandPoseObservation, along with some careful camera placement I was able to fake hand tracking, in 2D, on the simulator.</p><p>So how does this magic work? Here&#8217;s how it looks from another angle, without the green screen effect:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bkUU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bkUU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 424w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 848w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 1272w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bkUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png" width="1456" height="658" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:658,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2577202,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bkUU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 424w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 848w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.png 1272w, https://substackcdn.com/image/fetch/$s_!bkUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23588f9-1997-4804-b0e8-b0cd1b54a52b_2664x1204.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">Screen capture of an Xcode Vision preview, with a 3/4 view of the app, showing a video of a hand, with dots for the hand joints overlayed in the z axis. </figcaption></figure></div><p>There are three parts to this, one the video, two the video filter and three the hand detection.</p><h2>Video</h2><p>First I recorded a clip of just my hand, on a &#8220;green screen&#8221; (my practice putting mat) and imported that into the Xcode Project (After first resaving it to remove the HDR element as that seemed to mess up playback).</p><p>Using AVFoundation and an AVPlayer in a UIViewControllerRepresentable I was able to get video playback, but in a way I could also add a hook to the AVPlayerItemVideoOutput necessary to access the frame buffer for the video.</p><pre><code>struct PlayerView: UIViewControllerRepresentable {
    
    @Binding var url: URL?
    
    func updateUIViewController(_ uiView: UIViewController, context: UIViewControllerRepresentableContext&lt;PlayerView&gt;) {
    }

    func makeUIViewController(context: Context) -&gt; UIViewController {
        let view = PlayerUIView(frame: .zero,
                                url: $url)
        let controller = PlayerController()
        controller.view = view
        
        return controller
    }
}</code></pre><pre><code>class PlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()
    private let _myVideoOutputQueue = DispatchQueue(label: "VideoFrames", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
    
    lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidRefresh(link:)))
    var player: AVPlayer
    var videoOutput: AVPlayerItemVideoOutput
    
    @Binding var url: URL?
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    init(frame: CGRect,
         url: Binding&lt;URL?&gt;,
         handObservable: HandObservable) {
        
        _url = url
        // Setup the player
        player = AVPlayer(url: url.wrappedValue!)
        
        let settings = [ String(kCVPixelBufferPixelFormatTypeKey): kCVPixelFormatType_32BGRA ]
        let output = AVPlayerItemVideoOutput(pixelBufferAttributes: settings)
        self.videoOutput = output
        super.init(frame: frame)
        
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspect
        layer.addSublayer(playerLayer)
        
        attachVideoComposition()
        
        player.currentItem?.add(output)
        displayLink.add(to: .main, forMode: .common)

        // Start the movie
        player.play()
    }
}</code></pre><p>This is mostly boilerplate, but the important pieces of code here are hidden in &#8216;attachVideoComposition&#8217; for adding the alpha along with the displayLink setup for processing the individual frames with Vision.</p><div><hr></div><h2>Filter</h2><p>The attachVideoComposition function creates a CIColorCube filter, which converts anything of a certain colour hue (in our case green) to alpha. </p><pre><code>private func attachVideoComposition() {
        //remove green hues
        let chromaCIFilter = self.chromaKeyFilter(fromHue: 0.2, toHue: 0.5)

        if let playerItem = player.currentItem {
            let asset = playerItem.asset
            
            AVMutableVideoComposition.videoComposition(with: asset) { filteringRequest in
                let source = filteringRequest.sourceImage
                chromaCIFilter?.setValue(source, forKey: kCIInputImageKey)

                // Apply CoreImage provessing here
                filteringRequest.finish(with: chromaCIFilter?.outputImage ?? source, context: nil)
            } completionHandler: { composition, error in
                playerItem.videoComposition = composition
            }
        }
    }</code></pre><p>The filter itself is directly taken from Apple&#8217;s <a href="https://developer.apple.com/documentation/coreimage/applying_a_chroma_key_effect">sample code here</a>, which couldn&#8217;t have been more perfect for what I needed.</p><p>That gave me a nice transparent version of the video, once I&#8217;d tweaked the `fromHue` and `toHue` values to something that matched my specific green.</p><p>The player view is loaded in a simple SwiftUI View within the project, that loads the local video file on the press of a button.</p><pre><code>struct VideoViewDemo: View {

    @State private var url: URL?
    
    var body: some View {
                ZStack {
                    if url != nil {
                        PlayerViewDemo(url: $url)
                    } else {
                        Rectangle()
                            .stroke(.green)
                            .frame(width: 326)
                        Button(action: {
                            url = Bundle.main.url(forResource: "IMG_2067", withExtension: "mov")
                            print("url:\(url?.absoluteString ?? "no file")")
                            }) {
                            Text("Play")
                                .font(.headline)
                                
                        }
                    }
                }.edgesIgnoringSafeArea(.all)
    }
}</code></pre><p>I&#8217;ve since come across a much more <a href="https://developer.apple.com/documentation/visionos/destination-video/">robust video playback</a> example from Apple themselves and is a better starting point for video control than shown here.</p><p></p><p>The only other thing I needed to change was adding the .windowStyle(.plain) modifier to the WindowGroup, otherwise I still had the glass effect on the app&#8217;s window.</p><p>Here&#8217;s the result of the transparency side by side with the raw video:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!odir!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!odir!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 424w, https://substackcdn.com/image/fetch/$s_!odir!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 848w, https://substackcdn.com/image/fetch/$s_!odir!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 1272w, https://substackcdn.com/image/fetch/$s_!odir!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!odir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png" width="1456" height="580" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:580,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4096980,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!odir!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 424w, https://substackcdn.com/image/fetch/$s_!odir!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 848w, https://substackcdn.com/image/fetch/$s_!odir!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.png 1272w, https://substackcdn.com/image/fetch/$s_!odir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d4d6b60-3d7f-4a6c-ae79-a1000eb21fc0_2704x1078.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">comparison of two screenshots, the left the source video, the right with the green background replaced with transparency</figcaption></figure></div><div><hr></div><h2>VisionKit</h2><p>That was pretty cool as it was, but I also wanted to see if I could run any of the confusingly named <a href="https://developer.apple.com/documentation/visionkit">Vision</a> detection algorithms on the video as well. This is an existing framework, dedicated to extracting clever things from images and video, unrelated to VisionOS, Vision Pro. That&#8217;s where the DisplayLink code comes in, which gives us notification every time a new video frame has been shown, and means we can extract a pixel buffer if it&#8217;s available. The code to do this is:</p><pre><code>@objc func displayLinkDidRefresh(link: CADisplayLink) {
        let itemTime = player.currentTime()
        if videoOutput.hasNewPixelBuffer(forItemTime: itemTime) {
            var presentationItemTime: CMTime = .zero
            if let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: itemTime, itemTimeForDisplay: &amp;presentationItemTime) {
                // process the pixelbuffer here
                processImage(pixelBuffer: pixelBuffer)
            }
        }
    }</code></pre><p>With a pixel buffer available, which is one input that Vision can process, we are able to spin up a VNDetectHandPoseRequest, retrieve the observations, filter for those that have a high confidence, and finally store those in a location we can get to from the SwiftUI view. There&#8217;s a lot going on here, and I&#8217;ll dig more into the details of Vision and how the different detectors work in future posts, but it doesn&#8217;t take much code to get this working.</p><pre><code>private var currentBuffer: CVPixelBuffer?
    
    func processImage(pixelBuffer: CVPixelBuffer) {
//the request can take longer than frames are sent to it, so only process one at a time
        guard currentBuffer == nil else {
            return
        }
        currentBuffer = pixelBuffer
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
        DispatchQueue.global(qos: .userInteractive).async {
            do {
                let handRequest = VNDetectHumanHandPoseRequest()
                handRequest.maximumHandCount = 1
                defer { self.currentBuffer = nil }
                try handler.perform([handRequest])
                
                if let observation = handRequest.results?.first as? VNHumanHandPoseObservation {
                    let allPoints = try observation.recognizedPoints(.all)
// filter for high confidence observations
                    let filtered = allPoints.filter { observation in
                        observation.value.confidence &gt; 0.5
                    }
                    
//                    print("got hand: \(filtered.count)")
                    
                    DispatchQueue.main.async {
//send to an observable class on the main thread
                        self.handObservable.handPoints = Array(filtered.values)
                    }
                }
            } catch {
                print("error: \(error.localizedDescription)")
            }
        }
    }</code></pre><p>Lastly we take these observations and display them back to the user. One thing to note, is these observations are only in 2D, so of limited use for a full Vision application. The code here isn&#8217;t great, with some hardcoded values for the size of the window and video frame offsets. In reality you should convert the points from the Vision step (x: 0.0...1.0, y: 1.0...0.0) onto the video frame accurately, more on that in another post. But for this demo these value work well enough to get across the idea.</p><pre><code>Canvas { context, size in
                                for point in handObservable.handPoints {
                                    let xWidth = ((size.width - 326)/2)
                                    let path = Path(ellipseIn:
                                                        CGRect(origin: CGPoint(x: (point.x * 326) + xWidth, y: 620 - (580 * point.y)), size: CGSize(width: 8, height: 8)))
                                    
                                    context.fill(path, with: .color(.green))
                                }
                            }</code></pre><p>And that&#8217;s it, a pretty convincing effect once you carefully move the viewing position of the window to make sure it&#8217;s in the bottom left of the screen.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HSIw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HSIw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HSIw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg" width="1456" height="1160" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1160,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:682326,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HSIw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HSIw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6df1cbe-8b34-4226-a0c1-e8294b930b9b_2184x1740.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><figcaption class="image-caption">Hand tracking in the Vision Pro Simulator</figcaption></figure></div><p>The Vision performance isn&#8217;t great (but is also completely unoptimised, running on a 4k video feed) so there&#8217;s a bit of a lag but for a POC it works surprisingly well.</p><h2>So what?</h2><p>Okay, so what use is this anyway? Well, it should be possible to convert these 2D points into a estimated 3D skeleton that can act like the actual hands detected natively in Vision. If I can get that to work, then it will enable a workflow of pre-recording some hand videos, and using them for testing actual 3D hand tracked gestures and experiences in the Simulator. That same workflow will also be useful for testing experiences using VisionOS detected hands, which already come with 3D data. The ability to &#8220;record&#8221; hand positions and re-play them back over and over will make testing Vision Pro apps and games easier in the long run.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Multiple Cameras in VisionOS Previews]]></title><description><![CDATA[Make it easy to view your content from anywhere]]></description><link>https://varrall.substack.com/p/multiple-cameras-in-visionos-previews</link><guid isPermaLink="false">https://varrall.substack.com/p/multiple-cameras-in-visionos-previews</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Wed, 12 Jul 2023 01:16:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pG5P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve had a play around with the Preview of Vision content within Xcode, you&#8217;ll know how powerful it can be to see instant feedback for changes you&#8217;re making in code. It&#8217;s a particular impressive experience, given the complexities of 3D content that can be shown, in near real time.</p><p>However if like me you&#8217;ll also quickly become frustrated with how cumbersome it can be to navigate the simulated world, particularly if you have to move around your content regularly to validate the effect your trying to create. There are a number of built in cameras to the preview that you can jump between, but what if you need a specific angle not covered by these?</p><p>That&#8217;s where <a href="https://developer.apple.com/documentation/developertoolssupport/previewcamera">preview cameras</a> come in, which were introduced in Xcode 15.0 beta 3. With the new #Preview macro, you are able to define your own PreviewCamera&#8217;s with options for setting a direction, or more usefully setting a camera location, looking towards another location.</p><p>To use these camera previews, you will ned to add them to the #Preview macro as follows:</p><pre><code>BEFORE:
#Preview {
    MyModelView()
}

AFTER:
#Preview(body: {
    MyModelView()
}, cameras: {
    PreviewCamera(from: .topTrailingFront,
                  zoom: 0.5,
                  name: "cam0")
    PreviewCamera(lookingAt: Point3D(x: 0, y: -1, z: 2),
                  from: Point3D(x: 1, y: 0, z: 2),
                  name: "cam1")

})</code></pre><div><hr></div><p>Here we&#8217;re creating two Preview Cameras. The first is using a <a href="https://developer.apple.com/documentation/SwiftUI/UnitPoint3D">UnitPoint3D</a>, to specify a position to look down on the content from, whilst also zooming out from the content. The second is a more flexible way to define a camera. It&#8217;s looking at a point that is near the ground and a 2m away from the back wall of the room (which seems to be the 0 position), with the camera&#8217;s position (&#8220;from&#8221;) being 1m to the side, and on the ceiling (defined as 0m). With the second option, you don&#8217;t have the ability to set the zoom level specifically.</p><p>When you start the preview, it will now use the first camera you set as the PreviewCamera, but you can switch between them easily using the camera button in the lower right of the Preview Canvas.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pG5P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pG5P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 424w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 848w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pG5P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png" width="1184" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1184,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1743569,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pG5P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 424w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 848w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!pG5P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e01cde2-b0dd-4e53-b567-72a046a21bdd_1184x1048.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>You can add as many camera&#8217;s as you like, just be sure to give them a unique name so that you can differentiate them in the UI.</p><p>Happy Viewing!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://varrall.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 StuVision: Exploration of Apple's Vision Pro! 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[Testing edge cases]]></title><description><![CDATA[The personal cricket app I made for myself gets daily use.]]></description><link>https://varrall.substack.com/p/testing-edge-cases</link><guid isPermaLink="false">https://varrall.substack.com/p/testing-edge-cases</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Fri, 27 Jan 2023 11:50:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f1cae537-8083-4b68-a779-025f922e804c_1170x2532.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The personal cricket app I made for myself gets daily use. It means I&#8217;m finding edge cases which is breaking things. Some recent issues have been when the api isn&#8217;t working as expected, so huge chunks of expected data is missing (and crashing the app) or just where the game state causes weird issues with displaying the data.</p><p>I wish Swift Playgrounds had a better way of running tests, I really don&#8217;t want to fall back to Xcode just for this, having written the whole app on the iPad so far&#8230;</p>]]></content:encoded></item><item><title><![CDATA[Coming soon]]></title><description><![CDATA[This is StuVision: Exploration of Apple&#39;s Vision Pro, a newsletter about Forays into mobile app development.]]></description><link>https://varrall.substack.com/p/coming-soon</link><guid isPermaLink="false">https://varrall.substack.com/p/coming-soon</guid><dc:creator><![CDATA[Stuart Varrall]]></dc:creator><pubDate>Tue, 13 Dec 2022 04:00:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PG14!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ebd42a-f2a8-4f2e-8de1-b354406c0a4f_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>This is StuVision: Exploration of Apple&#39;s Vision Pro</strong>, a newsletter about Forays into mobile app development.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://varrall.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://varrall.substack.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item></channel></rss>