{"id":801,"date":"2023-09-27T20:32:07","date_gmt":"2023-09-27T18:32:07","guid":{"rendered":"https:\/\/mic.st\/blog\/?p=801"},"modified":"2024-02-08T00:24:58","modified_gmt":"2024-02-07T23:24:58","slug":"swiftui-ornaments-modifier-for-visionos-app-on-vision-pro","status":"publish","type":"post","link":"https:\/\/mic.st\/blog\/swiftui-ornaments-modifier-for-visionos-app-on-vision-pro\/","title":{"rendered":"SwiftUI .ornaments modifier for visionOS App on Vision Pro"},"content":{"rendered":"\n<p>Currently, I am experimenting on a Vision Pro app as a side project. One thing I was not aware of for quite some time was this new paradigm with the fancy name <em>Ornaments<\/em> only used on visionOS. I just happily added buttons near the bottom edge of my view. However, using an ornament is the way to go. It is basically a kind of elevated group of controls at the bottom of the view. You can read more on it in Apple&#8217;s Human Interface Guidelines (HIG): <a href=\"https:\/\/developer.apple.com\/design\/human-interface-guidelines\/ornaments\/\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/design\/human-interface-guidelines\/ornaments\/<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using new SwiftUI features<\/h2>\n\n\n\n<p>If you want to learn about other new SwiftUI features you can use for making your visionOS app feeling &#8220;more home&#8221;, please checkout my other article: <br><a href=\"https:\/\/mic.st\/blog\/develop-and-release-a-visionos-app-without-vision-pro\/\">https:\/\/mic.st\/blog\/develop-and-release-a-visionos-app-without-vision-pro\/<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to implement ornaments<\/h2>\n\n\n\n<p>When I first tried implementing my ornament, it just did not work. And I could not really find any useful information on why it did not work. What I found out about after sleeping over it: Your scene must not be of window style must not be <code>.volumetric<\/code> it has to be <code>.automatic<\/code> (or plain). If it&#8217;s your initial screen also make sure to set <code>UIWindowSceneSessionRoleApplication<\/code> for the key <code>UIApplicationPreferredDefaultSceneSessionRole<\/code> inside of your info.plist.<\/p>\n\n\n\n<p>This means your info.plist should have this content:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dict<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">key<\/span>&gt;<\/span>UIApplicationSceneManifest<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">key<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dict<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">key<\/span>&gt;<\/span>UISceneConfigurations<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">key<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dict<\/span>\/&gt;<\/span>\n<\/span><\/span><mark class='shcb-loc'><span>\t\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">key<\/span>&gt;<\/span>UIApplicationPreferredDefaultSceneSessionRole<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">key<\/span>&gt;<\/span>\n<\/span><\/mark><mark class='shcb-loc'><span>\t\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">string<\/span>&gt;<\/span>UIWindowSceneSessionRoleApplication<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">string<\/span>&gt;<\/span>\n<\/span><\/mark><span class='shcb-loc'><span>\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dict<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dict<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And your initial <code>WindowGroup<\/code> should look similar to this (so you can still set the view&#8217;s bounds if you want):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\"><span class=\"hljs-type\">WindowGroup<\/span>(id: someIDIfNeeded) {\n\t<span class=\"hljs-type\">MyFunkyView<\/span>()\n}\n.windowStyle(.automatic) <span class=\"hljs-comment\">\/\/ or .plain<\/span>\n.defaultSize(<span class=\"hljs-type\">Size3D<\/span>(width: <span class=\"hljs-number\">1.8<\/span>, height: <span class=\"hljs-number\">1<\/span>, depth: <span class=\"hljs-number\">0.1<\/span>), <span class=\"hljs-keyword\">in<\/span>: .meters)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The following won&#8217;t work. Or at least the ornament will not be shown:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\"><span class=\"hljs-type\">WindowGroup<\/span>(id: someIDIfNeeded) {\n\t<span class=\"hljs-type\">MyFunkyView<\/span>()\n}\n.windowStyle(.volumetric)\n.defaultSize(<span class=\"hljs-type\">Size3D<\/span>(width: <span class=\"hljs-number\">1.8<\/span>, height: <span class=\"hljs-number\">1<\/span>, depth: <span class=\"hljs-number\">0.1<\/span>), <span class=\"hljs-keyword\">in<\/span>: .meters)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As soon as you have this set up, you can just use the <code>.ornaments<\/code> modifier e.g. on your <code>NavigationStack<\/code> like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\">.ornament(visibility: .visible, attachmentAnchor: .scene(.bottom), contentAlignment: .center) {\n\t<span class=\"hljs-type\">HStack<\/span>(spacing: <span class=\"hljs-number\">16<\/span>) {\n\t\t<span class=\"hljs-type\">Text<\/span>(<span class=\"hljs-string\">\"HELLO ORNAMENT \ud83d\ude80\"<\/span>)\n\t\t<span class=\"hljs-type\">Button<\/span> {\n\t\t\t<span class=\"hljs-built_in\">print<\/span>(<span class=\"hljs-string\">\"Tapped that!\"<\/span>)\n\t\t} label: {\n\t\t\t<span class=\"hljs-type\">Label<\/span>(<span class=\"hljs-string\">\"Big Button\"<\/span>, systemImage: <span class=\"hljs-string\">\"hare\"<\/span>)\n\t\t\t\t.padding(<span class=\"hljs-number\">16<\/span>)\n\t\t}\n\t}\n\t.font(.extraLargeTitle)\n\t.padding(<span class=\"hljs-number\">32<\/span>)\n\t.glassBackgroundEffect()\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Eventually, this is how the ornament above will look like:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"362\" height=\"87\" src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2023\/09\/image.png\" alt=\"Screenshot of visionOS app showing an ornament rendered from code previously shown in this post.\" class=\"wp-image-802\" srcset=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2023\/09\/image.png 362w, https:\/\/mic.st\/blog\/wp-content\/uploads\/2023\/09\/image-300x72.png 300w\" sizes=\"auto, (max-width: 362px) 100vw, 362px\" \/><figcaption class=\"wp-element-caption\">Rendering of the code above inside of the Vision Pro simulator<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>If your ornament is not show, make sure you are showing it on an <code>WindowGroup<\/code> using <code>.plain<\/code> window style. Otherwise, it is just not shown for some reason.<\/p>\n\n\n\n<p>Happy Coding! \ud83d\ude80<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Currently, I am experimenting on a Vision Pro app as a side project. One thing I was not aware of for quite some time was this new paradigm with the fancy name Ornaments only used on visionOS. I just happily added buttons near the bottom edge of my view. However, using an ornament is the&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-801","post","type-post","status-publish","format-standard","hentry","category-ios-development"],"_links":{"self":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/801","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/comments?post=801"}],"version-history":[{"count":4,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/801\/revisions"}],"predecessor-version":[{"id":860,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/801\/revisions\/860"}],"wp:attachment":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/media?parent=801"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/categories?post=801"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/tags?post=801"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}