<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Max Cobb</title>
    <description>The latest articles on Forem by Max Cobb (@maxxfrazer).</description>
    <link>https://forem.com/maxxfrazer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F450789%2Fe9b799a9-9703-4b82-88d9-d208ab97cba9.jpeg</url>
      <title>Forem: Max Cobb</title>
      <link>https://forem.com/maxxfrazer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/maxxfrazer"/>
    <language>en</language>
    <item>
      <title>What's New in Apple AR/VR 2023</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Wed, 07 Jun 2023 02:32:27 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/whats-new-in-apple-arvr-2023-33eb</link>
      <guid>https://forem.com/maxxfrazer/whats-new-in-apple-arvr-2023-33eb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;AR/VR/XR Highlights from Apple's WWDC Developer Conference 2023&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's been six years since ARKit's debut at WWDC 2017, and we've seen big milestones since then; but none come close to what was announced at WWDC 2023 this week.&lt;/p&gt;

&lt;p&gt;From a full AR/VR simulator, to Apple Vision Pro, to full skeletal hand tracking, you'll find the AR/VR highlights from the main keynote listed below; as well as other things hiding around in Apple's beta API References. It won't go into too much technical details on how to implement some of these new features and new frameworks, you'll have to &lt;a href="https://twitter.com/maxxfrazer"&gt;follow me on twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Although many of the APIs are not yet public (expected later in June 2023), we'll cover an outline of the main new features coming our way soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  ARKit
&lt;/h2&gt;

&lt;p&gt;Starting with the core of AR at Apple, there only seems to be one feature available for now, and only on the newly introduced OS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skeletal Hand Tracking
&lt;/h3&gt;

&lt;p&gt;As well as tracking surfaces, images and bodies, ARKit will now have skeletal hand tracking, starting with visionOS. This seems to be enabled due to a new device that has multiple cameras to better percieve depth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v-WemVxQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4w47nsvoigr3i7boali.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v-WemVxQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4w47nsvoigr3i7boali.gif" alt="A hand with a globe floating above" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  RealityKit
&lt;/h2&gt;

&lt;p&gt;Not known to be the fastest horse in terms of rolling out features, especially when compared to its older sister &lt;a href="https://developer.apple.com/scenekit/"&gt;SceneKit&lt;/a&gt;. But this year there were some much needed advancements, which were of course necessary for a certain piece of hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  RealityKit x SwiftUI
&lt;/h3&gt;

&lt;p&gt;With the introduction of a whole new SwiftUI view, RealityView, RealityKit can now fully take advantage of SwiftUI layouts, and vice versa.&lt;/p&gt;

&lt;p&gt;You can also add SwiftUI views into RealityKit in the form of attachments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LBfgdFGs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn4mvp1efsd0n57e0qmn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LBfgdFGs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn4mvp1efsd0n57e0qmn.gif" alt="Rendering of an island dipslayed in AR, with a couple of SwiftUI views added as attachments" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This now allows us to add content with sharp, readable text, especially those with complex layouts and animations.&lt;/p&gt;

&lt;h3&gt;
  
  
  MaterialX
&lt;/h3&gt;

&lt;p&gt;RealityKit is adopting the open standard MaterialX for surface and geometry shaders.&lt;/p&gt;

&lt;p&gt;MaterialX is supported across a broad variety of tools. Meaning people already familiar with those tools can now write already shaders in RealityKit, as well as those already using RealityKit can now have more developer tools and countless shader examples at their disposal.&lt;/p&gt;

&lt;h2&gt;
  
  
  visionOS
&lt;/h2&gt;

&lt;p&gt;Apple's exciting new operating system, visionOS brings many of the AR/VR concepts that RealityKit developers may be familiar with together in a stunning new headset dubbed "Vision Pro". While many of the &lt;/p&gt;

&lt;h3&gt;
  
  
  Fundamentals
&lt;/h3&gt;

&lt;p&gt;There are a few key objects that you can create within Vision Pro's OS, visionOS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows, planes in space.&lt;/li&gt;
&lt;li&gt;Volumes, which can hold 3d objects, such as a game board.&lt;/li&gt;
&lt;li&gt;Full Space, which are fully immersive environments, and can not necessarily be manipulated by the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ONBAYmZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yajk7u0k9r36g74uqmpe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ONBAYmZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yajk7u0k9r36g74uqmpe.jpg" alt="Screenshot of the platforms state of the union presentation showing the three fundamentals of visionOS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Apps
&lt;/h3&gt;

&lt;p&gt;Existing iOS/iPadOS apps are supported as a scalable 2d window shown in 3d space on visionOS. This window keeps their original look and feel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RqNw92W2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5er7j44ofvr88sthb93w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RqNw92W2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5er7j44ofvr88sthb93w.gif" alt="Existing iPad application, brought into a 3d scene with visionOS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you simply add the build target of visionOS to your application, it immediately gets those nice visionOS materials, a fully resizable window, and displays the eye focus of the user, as you find in all visionOS applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S5R3Q_8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7aqyk2y16nmbnixa9n7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S5R3Q_8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7aqyk2y16nmbnixa9n7.gif" alt="Updated iPad application, with visionOS materials" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With visionOS SwiftUI or UIKit can be used display content, RealityKit for 3d content, and ARKit to understand the user's space. The apps designed specifically for visionOS can create a collection of windows, volumes, and spaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simulator
&lt;/h3&gt;

&lt;p&gt;My favourite part of the developer tooling at WWDC 2023. This simulator lets you move and look around the scene, and interact with the app by simulating the system gestures. There are several different builtin scenes, with both day and night coditions to help better understand how users will interact with your apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--51b3r44i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o545s5wfwg6g3tg4or8x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--51b3r44i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o545s5wfwg6g3tg4or8x.gif" alt="visionOS simulator, showing an application in the middle, camera is moving towards it and rotating slightly to show depth" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mac Virtual Display
&lt;/h3&gt;

&lt;p&gt;Simply by looking at your Mac through a Vision Pro, your Mac's screen will pop out and instead a high fidelity 4k monitor will appear in its place. This enables a whole new end-to-end development experience, where you can code, test, and debug all in the same place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lkeieOLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wui76t3tp17zlc7ciew8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lkeieOLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wui76t3tp17zlc7ciew8.jpg" alt="Visualisation of a mac window on the right containing Xcode, and the built visionOS application on the left. macbook with blank screen below both." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bluetooth keyboards and mice can be used for data input across visionOS, so of course you won't have to type on a virtual keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reality Composer Pro
&lt;/h3&gt;

&lt;p&gt;Using Reality Composer Pro, you can add and prepare 3d content for visionOS apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;You may have noticed some sections are a little bit bare, I'll be expanding upon the content over the next week or so as more videos and software are released.&lt;/p&gt;

&lt;p&gt;To stay tuned, drop a follow on &lt;a href="https://dev.to/maxxfrazer"&gt;dev.to&lt;/a&gt; as well as &lt;a href="https://twitter.com/maxxfrazer"&gt;twitter&lt;/a&gt;. I'm always posting about the latest advancements in AR/VR, and specifically those in the Swift ecosystem.&lt;/p&gt;

</description>
      <category>realitykit</category>
      <category>visionpro</category>
      <category>visionos</category>
      <category>wwdc23</category>
    </item>
    <item>
      <title>Getting Started with RealityKit: Brilliant Colours</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Mon, 15 May 2023 01:50:24 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/getting-started-with-realitykit-brilliant-colours-229d</link>
      <guid>https://forem.com/maxxfrazer/getting-started-with-realitykit-brilliant-colours-229d</guid>
      <description>&lt;p&gt;Have you ever noticed that the materials in RealityKit appear dull and lackluster? Even when using UnlitMaterials, the colors tend to look muted and unimpressive. In this article, we'll explore why this is the case and provide a solution to achieve more vibrant colors in your RealityKit scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Issue
&lt;/h2&gt;

&lt;p&gt;By default, the materials in RealityKit are designed to mimic real-world lighting conditions. This means that even unlit materials will have some degree of shading and shadowing, resulting in colors that appear less saturated than they would in a perfectly lit environment.&lt;/p&gt;

&lt;p&gt;While this approach may be appropriate for many use cases, such as architectural visualizations or product renderings, it can be frustrating when you're trying to create a more stylized or vibrant scene, or when just want some objects to stand out amongst the other objects that should blend into the scene.&lt;/p&gt;

&lt;p&gt;Take a look at this example, showing a simple sphere in a 3d scene. It looks okay, but in on way does that white colour stand out as a true white:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Y0v0jlx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4182rxgv26m84bkqsvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Y0v0jlx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4182rxgv26m84bkqsvw.png" alt="White cube on a rolling hill background, white is around 214 instead of 255" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual pixel value of the cube shows it's only 214 out of a maximum of 255 for true white.&lt;/p&gt;

&lt;p&gt;The same can be said when applying a texture.&lt;br&gt;
The colours are much more dull than they're intended to be, which is frustrating when wanting to make objects in your AR scene that really pop.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Fortunately, there's a simple solution to this problem that doesn't involve custom shaders, using the new(ish) material class &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial"&gt;PhysicallyBasedMaterial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This material type is actually made for simulating the appearance of real-world objects, but there's a trick to making it appear just like you would expect an &lt;a href="https://developer.apple.com/documentation/realitykit/unlitmaterial"&gt;UnlitMaterial&lt;/a&gt; to act, which is to set its &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial/basecolor-swift.property"&gt;baseColor&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial/sheen-swift.property"&gt;sheen&lt;/a&gt; to black, which then means the object will not respond to lighting, then apply your colour or texture to the &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial/emissivecolor-swift.property"&gt;emissiveColor&lt;/a&gt;, and bump up the &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial/emissiveintensity"&gt;emissiveIntensity&lt;/a&gt; to a value greater than its default of 1.&lt;/p&gt;

&lt;p&gt;Here's an example of what that might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;PhysicallyBasedMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sheen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emissiveColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emissiveIntensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--etABhSgO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ful0rxror1qntaae05d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--etABhSgO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ful0rxror1qntaae05d.png" alt="Duller white cube on the left, more vibrant one on the right" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The new material is on the right.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's what the pixel values are in the screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YoytmcdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uglnl9p186lzzy7bq4el.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YoytmcdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uglnl9p186lzzy7bq4el.gif" alt="Gif showing the different pixel RGB values of each cube" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, instead of setting the color in the emissiveColor, you can set a texture as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emissiveColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MaterialParameters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"rgb_test"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IGrjZPic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugusjatfehlvszr2qpo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IGrjZPic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugusjatfehlvszr2qpo2.png" alt="UnlitMaterial, SimpleMaterial and BoldMaterial" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows an UnlitMaterial on the left, a SimpleMaterial in the middle, and a BoldMaterial on the right, using this image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ix3m-2sV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvghtluxnfakuro9j3nh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ix3m-2sV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvghtluxnfakuro9j3nh.png" alt="Coloured image used for the material" width="259" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By making this small change, you'll be able to achieve more vibrant and true-to-life colors in your RealityKit scenes, without sacrificing the benefits of an unlit material.&lt;/p&gt;

&lt;p&gt;The type of &lt;code&gt;BoldMaterial&lt;/code&gt; is included in &lt;a href="https://github.com/maxxfrazer/RealityToolkit"&gt;RealityToolkit&lt;/a&gt;, which is a lightweight set of tools to make RealityKit more powerful, with remote image and USDZ file loading, and now BoldMaterial too.&lt;/p&gt;

&lt;p&gt;Here's a few examples with other colours, with the unlit material on the left, simple material in the middle, then the adaptation of the physically based material on the right:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nrTXn76n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z47jm266xx04843fe17j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nrTXn76n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z47jm266xx04843fe17j.png" alt="Three white cubes of varying contrast" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r2zmllOw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hbycq7sl3p6wrsim22r7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r2zmllOw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hbycq7sl3p6wrsim22r7.png" alt="Three red cubes of varying contrast" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N5eb2FlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/950hlclbu8sxocnjk4j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N5eb2FlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/950hlclbu8sxocnjk4j2.png" alt="Three green cubes of varying contrast" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But What About Lighting?
&lt;/h2&gt;

&lt;p&gt;This technique works best for materials that have no response to lighting, giving them an appearance that really stands out. However, if you do what your materials to respond to lighting, a few small tweaks could help. These tweaks include keeping the sheen as &lt;code&gt;.white&lt;/code&gt;, and increasing the baseColor to instead a dark gray.&lt;/p&gt;

&lt;p&gt;Play around with &lt;a href="https://developer.apple.com/documentation/realitykit/physicallybasedmaterial"&gt;PhysicallyBasedMaterial&lt;/a&gt; to find the exact appearance you're looking for in your RealityKit app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While RealityKit is a powerful tool for creating immersive AR and VR experiences, but things like this can sometimes make it more challenging than necesary to achieve the desired look and feel for your scenes. By using alternative type of material types, you can create more vibrant and engaging scenes that capture your user's attention.&lt;br&gt;
As mentioned above, check out the &lt;a href="https://github.com/maxxfrazer/RealityToolkit"&gt;RealityToolkit&lt;/a&gt; Swift Package to make adding bold materials to your project easy.&lt;/p&gt;

&lt;p&gt;I hope that this simple guide has helped you achieve the vibrant colors you're looking for in your RealityKit projects!&lt;/p&gt;

</description>
      <category>realitykit</category>
      <category>ios</category>
      <category>swift</category>
      <category>augmentedreality</category>
    </item>
    <item>
      <title>Create a Voice Changing Video Call App with SwiftUI</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Thu, 31 Mar 2022 16:45:46 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/create-a-voice-changing-video-call-app-with-swiftui-22b2</link>
      <guid>https://forem.com/maxxfrazer/create-a-voice-changing-video-call-app-with-swiftui-22b2</guid>
      <description>&lt;p&gt;Creating a video call app with Agora is super straightforward, but what about adding cool features to enhance the overall user experience or adding monetisation options? That's where Agora's &lt;a href="https://console.agora.io/marketplace" rel="noopener noreferrer"&gt;Extensions Marketplace&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;The Extensions Marketplace launched last month, and already several extensions are available, with more to come.&lt;/p&gt;

&lt;p&gt;In this post, we'll show you how to easily add a voice changer to your existing video call app and choose between the available voice options. Thanks to the &lt;a href="https://console.agora.io/marketplace/extension/introduce?serviceName=voicemod" rel="noopener noreferrer"&gt;Voicemod Voice Changer&lt;/a&gt;, in just three easy steps you will be able to modify your voice with this extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the extension&lt;/li&gt;
&lt;li&gt;Pass extension credentials&lt;/li&gt;
&lt;li&gt;Choose the voice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sso.agora.io/en/signup?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=create-a-voice-changing-video-call-app-with-swiftui" rel="noopener noreferrer"&gt;An Agora Developer Account - Sign up here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Xcode 12.3 or later&lt;/li&gt;
&lt;li&gt;A physical iOS device with iOS 13.0 or later&lt;/li&gt;
&lt;li&gt;A basic understanding of iOS development&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Let’s start with a new, single-view iOS project. Create the project in Xcode, choosing SwiftUI for the interface, and then add the &lt;a href="https://github.com/AgoraIO-Community/iOS-UIKit" rel="noopener noreferrer"&gt;Agora UIKit&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;Add the package by selecting &lt;code&gt;File &amp;gt; Swift Packages &amp;gt; Add Package Dependency&lt;/code&gt;, and then paste the link into this Swift package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/iOS-UIKit.git" rel="noopener noreferrer"&gt;&lt;code&gt;https://github.com/AgoraIO-Community/iOS-UIKit.git&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When asked for the version, insert &lt;code&gt;4.0.0-preview&lt;/code&gt;, and select "Up to Next Major". This should install at least version &lt;code&gt;4.0.0-preview.8&lt;/code&gt;, which is released at the time of writing.&lt;/p&gt;

&lt;p&gt;When in doubt, you can always select "Exact" and insert &lt;code&gt;4.0.0-preview.8&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Once that package installed, the camera and microphone usage descriptions need to be added. To see how to do that, check out Apple's documentation here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_ios#2962313" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_ios#2962313&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Call UI
&lt;/h2&gt;

&lt;p&gt;I won't go into much detail about the UI here, because we're mainly focusing on the Voicemod extension, but on a high level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ContentView contains two views: a VideoCallView (defined in the project, and a button to join the channel.&lt;/li&gt;
&lt;li&gt;ContentView holds the active AgoraViewer, which is brought in from Agora UIKit.&lt;/li&gt;
&lt;li&gt;When the button is pressed, the Agora UIKit method join(channel:,with:,as:) is called, which joins the Agora video channel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what the ContentView looks like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And the VideoCallView, without any of the voicemod additions looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;VideoCallView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;joinedChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ZStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;We now have a working video call app with SwiftUI and Agora UIKit. The next step is to integrate the Voicemod extension!&lt;/p&gt;
&lt;h2&gt;
  
  
  Integrating Voicemod
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Voicemod Credentials
&lt;/h3&gt;

&lt;p&gt;If you have an account with Agora and are currently signed in, follow &lt;a href="https://console.agora.io/marketplace/extension/introduce?serviceName=voicemod" rel="noopener noreferrer"&gt;this link&lt;/a&gt; to activate the Voicemod extension for your account.&lt;/p&gt;

&lt;p&gt;First, you will need to activate Voicemod for your account by clicking "Activate" here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F394h84s43ovles91741j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F394h84s43ovles91741j.jpg" alt="extension-activate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, enable Voicemod for the project, the same project as for the Agora App ID used in the app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhk5bgf4aap4wjie58mj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhk5bgf4aap4wjie58mj.jpg" alt="extension-enable-project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once that's done, you can grab your Voicemod &lt;code&gt;API Key&lt;/code&gt; and &lt;code&gt;API Secret&lt;/code&gt; by clicking “view” below the credentials tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9piqtcbqh2b8xg5ds91a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9piqtcbqh2b8xg5ds91a.jpg" alt="extension-credentials"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add to Xcode
&lt;/h3&gt;

&lt;p&gt;We need to add the Voicemod extension, which can also be installed via the Swift Package Manager with the following URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/Extension-Voicemod-iOS.git" rel="noopener noreferrer"&gt;&lt;code&gt;https://github.com/AgoraIO-Community/Extension-Voicemod-iOS.git&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The latest release at the time of writing is &lt;code&gt;0.1.2&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This package doesn't need importing into your Swift code. It just needs to be bundled alongside the Agora SDK in your app target.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the Extension
&lt;/h3&gt;

&lt;p&gt;When using Agora extensions, you need to enable them before joining a channel.&lt;/p&gt;

&lt;p&gt;To do this with Agora UIKit, you can call &lt;code&gt;AgoraVideoViewer.enableExtension()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This method needs to be passed the vendor ("Voicemod"), the vendor extension ("VoicemodExtension") in this case:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enableExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;withVendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Voicemod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"VoicemodExtension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above code snippet was added to the beginning of the &lt;code&gt;joinChannel&lt;/code&gt; method defined earlier.&lt;/p&gt;
&lt;h3&gt;
  
  
  Choose the Voice
&lt;/h3&gt;

&lt;p&gt;After joining the channel, we'll add a small menu to the top of our view. This menu will contain all the available voices from Voicemod. On selecting the voice, we'll call a method to make sure we're using that voice. On selecting an option, we update a state variable, &lt;code&gt;selection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The menu is created like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The names array is defined like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"baby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"cave"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"lost-soul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"robot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"titan"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the menu looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0qg67edd85kc5z4omf0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0qg67edd85kc5z4omf0.gif" alt="Voice Menu"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Almost there now! The only thing left to do is define the setVoicemodParam method.&lt;/p&gt;

&lt;p&gt;This method needs to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialise Voicemod, passing the API Key and API Secret values.&lt;/li&gt;
&lt;li&gt;Set the voice property so that it knows which voice effect you want to have.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, let's define a function &lt;code&gt;registerVoicemod&lt;/code&gt; for setting the key and secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;registerVoicemod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Set API Credentials&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dataDict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voicemodApiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"apiSecret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voicemodApiSecret&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;setPropertyResp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExtensionProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Voicemod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"VoicemodExtension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"vcmd_user_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;codable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dataDict&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;setPropertyResp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not set extension property"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voicemodRegistered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using the method &lt;code&gt;setExtensionProperty&lt;/code&gt; with the &lt;code&gt;vcmd_user_data&lt;/code&gt; key. The API Key and API Secret properties are stored in a Swift dictionary of type &lt;code&gt;[String: String]&lt;/code&gt;. This method from Agora UIKit allows us to pass a Swift dictionary, which is then encoded for us and passed to Agora's engine.&lt;/p&gt;

&lt;p&gt;If something goes wrong, either &lt;code&gt;setPropertyResp&lt;/code&gt; will be null or the response will be less than zero. But if all the steps are followed, no issues should arise!&lt;/p&gt;

&lt;p&gt;Now the method &lt;code&gt;setVoicemodParam&lt;/code&gt; can be defined. This method must call &lt;code&gt;registerVoicemod&lt;/code&gt; (first time only), and then set the voice value (robot, baby etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setVoicemodParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;voiceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voicemodRegistered&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerVoicemod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// SET VOICE&lt;/span&gt;
  &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExtensionProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Voicemod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"VoicemodExtension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"vcmd_voice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;strValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;voiceName&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have a video call app complete with the new extension from Voicemod!&lt;/p&gt;

&lt;p&gt;There are other extensions I'd encourage you to try out, and they can all be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://console.agora.io/marketplace" rel="noopener noreferrer"&gt;https://console.agora.io/marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same settings can be applied to an application made directly with the Agora SDK, but you will need to make sure you're using the 4.x version of the SDK. Details can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.agora.io/en/video-call-4.x-preview/landing-page?platform=iOS" rel="noopener noreferrer"&gt;https://docs.agora.io/en/video-call-4.x-preview/landing-page?platform=iOS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The methods, such as &lt;code&gt;setExtensionProperty&lt;/code&gt;, used in this post are adaptations of the SDK's built-in methods relating to extension. They can be found in this file from Agora Video UIKit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/iOS-UIKit/blob/4.0.0-preview.8/Sources/Agora-UIKit/AgoraVideoViewer%2BAgoraExtensions.swift" rel="noopener noreferrer"&gt;https://github.com/AgoraIO-Community/iOS-UIKit/blob/4.0.0-preview.8/Sources/Agora-UIKit/AgoraVideoViewer%2BAgoraExtensions.swift&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;You can try out this app by following the GitHub link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/Extension-Voicemod-SwiftUI" rel="noopener noreferrer"&gt;https://github.com/AgoraIO-Community/Extension-Voicemod-SwiftUI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Resources
&lt;/h2&gt;

&lt;p&gt;For more information about building applications using Agora SDKs, take a look at the &lt;a href="https://docs.agora.io/en/Video/start_call_ios?platform=iOS&amp;amp;utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=extension-voicemod-swiftui" rel="noopener noreferrer"&gt;Agora Video Call Quickstart Guide&lt;/a&gt; and &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=extension-voicemod-swiftui" rel="noopener noreferrer"&gt;Agora API Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also invite you to &lt;a href="https://www.agora.io/en/join-slack/" rel="noopener noreferrer"&gt;join the Agora Developer Slack community&lt;/a&gt; to meet with our developer team as well as other like-minded developers and technical enthusiasts.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>videocall</category>
      <category>programming</category>
    </item>
    <item>
      <title>Universal Links and SwiftUI Video Calls</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Tue, 15 Mar 2022 12:31:16 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/universal-links-and-swiftui-video-calls-hg</link>
      <guid>https://forem.com/maxxfrazer/universal-links-and-swiftui-video-calls-hg</guid>
      <description>&lt;p&gt;When making a video call application, you may want to share links to let your users' friends jump into the same video call or live stream that they're watching. The second-best way to achieve this is by sharing a room code for people to type in to their version of the app. But a much better option is to share a link that opens the app right to the place you want. That is where we can use universal links.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Universal Links?
&lt;/h2&gt;

&lt;p&gt;Often, if there is an equivalent web page for a view that exists in an app. For example, if you're viewing an Instagram profile on your phone's web browser, there is also a way to show the same content in the Instagram app. Universal links are the gateway for going from the web browser content to the app's equivalent content.&lt;/p&gt;

&lt;p&gt;In some cases, universal links can’t have a web equivalent; for example, a complex game that will not run in a web browser. A universal link in that scenario could lead to a template page, which can then be passed through to your application to render the full content or join the game.&lt;/p&gt;

&lt;p&gt;Check out Apple's documentation for more information on universal links:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/ios/universal-links/"&gt;https://developer.apple.com/ios/universal-links/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Universal Links also work on Android devices, but the setup on your website as well as the device is a little different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sso.agora.io/en/signup?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=universal-links-and-swiftui-video-calls"&gt;An Agora developer account - Sign up here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Xcode 12.3 or later&lt;/li&gt;
&lt;li&gt;A physical iOS device with iOS 13.0 or later&lt;/li&gt;
&lt;li&gt;A public server with an SSL certificate (https)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Universal Links
&lt;/h2&gt;

&lt;p&gt;Setting up universal links has been made easier in recent years, but it still has two main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting Up the Website&lt;/li&gt;
&lt;li&gt;Setting Up the App&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with the website side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Website
&lt;/h3&gt;

&lt;p&gt;This section is probably the easiest part of universal links. All you need to do is add a file named &lt;code&gt;apple-app-site-association&lt;/code&gt;, which contains the correct values for your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"applinks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"appIDs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DEVELOPMENT_TEAM.PRODUCT_BUNDLE_IDENTIFIER"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above example will put a universal link button on every page of your website. See below for how to specify specific URLs in your domain.&lt;/p&gt;

&lt;p&gt;You just need to replace DEVELOPMENT_TEAM and PRODUCT_BUNDLE_IDENTIFIER with the associated values from your app. To find them, you can run a couple of commands in a Terminal from your project's root folder, such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"DEVELOPMENT_TEAM"&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"PRODUCT_BUNDLE_IDENTIFIER"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your terminal will look similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"DEVELOPMENT_TEAM"&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"PRODUCT_BUNDLE_IDENTIFIER"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
./AppName.xcodeproj/project.pbxproj:                DEVELOPMENT_TEAM &lt;span class="o"&gt;=&lt;/span&gt; 278494H572&lt;span class="p"&gt;;&lt;/span&gt;
./AppName.xcodeproj/project.pbxproj:                PRODUCT_BUNDLE_IDENTIFIER &lt;span class="o"&gt;=&lt;/span&gt; io.agora.AppName&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the values we want are &lt;code&gt;278494H572&lt;/code&gt; and &lt;code&gt;io.agora.AppName&lt;/code&gt;, respectively.&lt;/p&gt;

&lt;p&gt;To check that your website is set up correctly, head to &lt;a href="https://branch.io/resources/aasa-validator/"&gt;branch.io's validator&lt;/a&gt;. One common issue is that the &lt;code&gt;content-type&lt;/code&gt; header cannot be found. If you face this issue, you will need to create or add these lines to &lt;code&gt;.htaccess&lt;/code&gt; at the same location as the &lt;code&gt;apple-app-site-association&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Files "apple-app-site-association"&amp;gt;
ForceType 'application/json'
&amp;lt;/Files&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;With the current setup, all pages on the website will link through to the specified app. If you just want a specific part of your website to link to an app, you can modify the components array object or add another one. For example, if the current components array is replaced with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/wwdc/news"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/videos/wwdc/2015/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then all the WWDC news articles would direct to the app, as well as all videos from 2015.&lt;/p&gt;

&lt;p&gt;More examples can be found in Apple's documentation &lt;a href="https://developer.apple.com/documentation/xcode/supporting-associated-domains"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For our app, we are going to add the following component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/join*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"?"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"?*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using example.com as our domain address, this is what an example of the full URL might look like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.example.com/join?channel=dkvn3lw2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For more information about how to set up universal links with an &lt;code&gt;apple-app-site-association&lt;/code&gt; file, Apple has a great resource from WWDC 2019 &lt;a href="https://developer.apple.com/videos/play/wwdc2019/717"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"applinks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"appIDs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DEVELOPMENT_TEAM.PRODUCT_BUNDLE_IDENTIFIER"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/join/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"?"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"?*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that that's set up, let's get to the app portion of our app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the App
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Installing the Package
&lt;/h4&gt;

&lt;p&gt;Set up a new SwiftUI app in Xcode, and add the following Swift package to your project with this URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://github.com/AgoraIO-Community/iOS-UIKit.git&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The current stable release of Agora UIKit is 1.7.1&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're not sure how to add a Swift package, Apple's documentation has it covered with clear steps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app"&gt;https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting the Views
&lt;/h4&gt;

&lt;p&gt;We'll get this part out of the way quickly, as we want to get back to the universal links part of this guide. The first thing we need to do is add a button to create and join a channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// click action here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Create Channel"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rRvBCIxX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/randwwfkwdcgc2b7mtbk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rRvBCIxX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/randwwfkwdcgc2b7mtbk.jpeg" alt="Create Channel button" width="559" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This button's action should create a channel name and display an AgoraViewer. AgoraViewer is a subclass of View that is provided by Agora UIKit. This can be achieved with the following block of code:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isShowingVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;videoCallView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Other buttons will be placed here */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;agview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraViewer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AgoraViewer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;connectionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraConnectionData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="kt"&gt;Agora&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="kt"&gt;Id&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rtcToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="kt"&gt;Agora&lt;/span&gt; &lt;span class="kt"&gt;Token&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floating&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}()&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;NavigationView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;ZStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videoCallView&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationBarHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAppear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* appear action */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDisappear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* disappear action */&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="nv"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;isShowingVideo&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;EmptyView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kt"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShowingVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Create Channel"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[//]: &amp;lt;&amp;gt; "&lt;a href="https://gist.github.com/maxxfrazer/d5cfacc3ba45e174dfe8168787f0075b?file=Agora-Univeral-Links-App-Shell.swift"&gt;https://gist.github.com/maxxfrazer/d5cfacc3ba45e174dfe8168787f0075b?file=Agora-Univeral-Links-App-Shell.swift&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Above, the channel is set to a static string &lt;code&gt;"test"&lt;/code&gt;, and the property isShowingVideo is set to true. The "test" string will be replaced with a random string generator in the final product on GitHub.&lt;/p&gt;

&lt;p&gt;isShowingVideo is a boolean which is used to decide if the video call view (&lt;code&gt;videoCallView&lt;/code&gt;) should be presented or not by the NavigationView. The next things to add are quite straightforward, we need to join and leave the channel using the onAppear and onDisappear methods, and add some buttons to the top of the camera view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAppear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelName&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShowingVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcaster&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDisappear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leaveChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If channelName is nil when the video view is appearing then we will close it again, this is because a channel name is required, so something must have gone wrong getting to this stage. After that we call the builtin function from Agora UIKit to join the channel as a broadcaster.&lt;/p&gt;

&lt;p&gt;The second parameter in the join method is for a token, which I am not using in my test application, but is required for production apps that use Agora.&lt;/p&gt;

&lt;p&gt;To find out more about working with tokens using Agora's SDKs, here are a few resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.agora.io/en/Interactive%20Broadcast/token_server"&gt;Authenticate Your Users with Tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.agora.io/en/blog/connecting-to-agora-with-tokens-using-swift/"&gt;Connecting to Agora with Tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Now the final part of the UI is setting up the other buttons in that &lt;code&gt;videoCallView&lt;/code&gt; above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;videoCallView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShowingVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kt"&gt;Spacer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="kt"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;urlShare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/join?channel=&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;activityVC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIActivityViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;activityItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;urlShare&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="nv"&gt;applicationActivities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;scenes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connectedScenes&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;windowScene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scenes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;UIWindowScene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;windowScene&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootViewController&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityVC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;animated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Share"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agview&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[//]: &amp;lt;&amp;gt; "&lt;a href="https://gist.github.com/maxxfrazer/d5cfacc3ba45e174dfe8168787f0075b?file=Agora-Univeral-Links-App-Buttons.swift"&gt;https://gist.github.com/maxxfrazer/d5cfacc3ba45e174dfe8168787f0075b?file=Agora-Univeral-Links-App-Buttons.swift&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Two buttons have been added in the above code snippet, and since they're positioned in a &lt;code&gt;HStack&lt;/code&gt;, they appear side by side, with a &lt;code&gt;Spacer()&lt;/code&gt; in-between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rJX_L7GG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vf7f65q6010jcjmwig1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rJX_L7GG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vf7f65q6010jcjmwig1.jpg" alt="Video Call Screenshot" width="512" height="1108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first button lets the user exit the current view, leaving the call and going back to the main screen. The second button creates a URL (using example.com as the domain) with the channel as a parameter. This generated URL will open up the default share screen, to send it natively via iMessage, AirDrop, email, etc.&lt;/p&gt;

&lt;p&gt;The share screen looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yH1DHdEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qqb7wzq4iy1n9qbe9gs.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yH1DHdEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qqb7wzq4iy1n9qbe9gs.jpeg" alt="Share Link Screenshot" width="880" height="1904"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Interpreting the Universal Link
&lt;/h3&gt;

&lt;p&gt;To let your app interpret a Universal link, your app must know which domains it is allowed to talk to. This is a very straightforward project, and can now be done directly with Xcode.&lt;/p&gt;

&lt;p&gt;Head to the Signing &amp;amp; Capabilities section of your app within Xcode, and add the Associated Domains capability like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BrvnCHYk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9amqovrddnggntkp5d54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BrvnCHYk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9amqovrddnggntkp5d54.png" alt="Adding associated domains capability" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once that's done, set the domains within the new capability, here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_v4TBcOo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vws8kaqq227ngy2erdkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_v4TBcOo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vws8kaqq227ngy2erdkg.png" alt="Setting associated domain property" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above entries say &lt;code&gt;applinks:example.com&lt;/code&gt; and &lt;code&gt;applinks:www.example.com&lt;/code&gt;. Replace “example.com” with your domain that is hosting the &lt;code&gt;apple-app-site-association&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final step in this application is understanding when the app has been opened from a universal link, and getting the full URL used to open the app.&lt;/p&gt;

&lt;p&gt;SwiftUI offers a very easy to implement method for capturing universal links that have opened the app, which is the &lt;a href="https://developer.apple.com/documentation/swiftui/view/onopenurl(perform:)"&gt;onOpenURL&lt;/a&gt; method. This method can be appended to any &lt;a href="https://developer.apple.com/documentation/swiftui/view"&gt;View&lt;/a&gt;, and its only parameter is the URL object. We can attach this to our NavigationView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;NavigationView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Navigation View content here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onOpenURL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absoluteString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When opening our app via the universal link, the above code block will execute and print our full URL string.&lt;/p&gt;

&lt;p&gt;All we really need from this is that channel parameter, the final part of the URL.&lt;/p&gt;

&lt;p&gt;To extract parts of the URL into a dictionary, I am calling a property called &lt;code&gt;queryDictionary&lt;/code&gt;, which has been grabbed from the following Stack Overflow answer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/a/46603619/2156765"&gt;https://stackoverflow.com/a/46603619/2156765&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;queryDictionary&lt;/code&gt;, we can get our channel name, and once again trigger the video feed to open as so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queryDictionary&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShowingVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app is complete! Now when going to your domain address with the endpoint &lt;code&gt;join?channel=test&lt;/code&gt;, it will launch your application and go straight to that channel name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y7EM47iM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pa440yfvqfykhbhnhodc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y7EM47iM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pa440yfvqfykhbhnhodc.gif" alt="Clicking link to open video call" width="443" height="960"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;This entire project is available on GitHub here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/Universal-Links-SwiftUI"&gt;https://github.com/AgoraIO-Community/Universal-Links-SwiftUI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When running the application, be sure to head over to the Signing and Capabilities section to update the domain (it will be set to example.com by default). Also once changing the Bundle and Team IDs, run the following command in Terminal to get your new values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"DEVELOPMENT_TEAM"&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"PRODUCT_BUNDLE_IDENTIFIER"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other Resources
&lt;/h2&gt;

&lt;p&gt;For more information about building applications using Agora SDKs, take a look at the &lt;a href="https://docs.agora.io/en/Video/start_call_ios?platform=iOS&amp;amp;utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=universal-links-and-swiftui-video-calls"&gt;Agora Video Call Quickstart Guide&lt;/a&gt; and &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=universal-links-and-swiftui-video-calls"&gt;Agora API Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also invite you to &lt;a href="https://www.agora.io/en/join-slack/"&gt;join the Agora Developer Slack community&lt;/a&gt; to meet with our developer team as well as other like-minded developers and technical enthusiasts.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>video</category>
      <category>swift</category>
    </item>
    <item>
      <title>Agora Releases Native SDK v3.5.1</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Mon, 18 Oct 2021 09:29:05 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/agora-releases-native-sdk-v351-4fhl</link>
      <guid>https://forem.com/maxxfrazer/agora-releases-native-sdk-v351-4fhl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Agora Native SDK 3.5.1 was released on 14 October 2021.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  New Features ✨
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Super Resolution (beta) 🪟
&lt;/h3&gt;

&lt;p&gt;Effectively boost the resolution of a remote user's video seen by the local user.&lt;/p&gt;

&lt;p&gt;You can call &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/enableRemoteSuperResolution:enabled:"&gt;&lt;code&gt;enableRemoteSuperResolution&lt;/code&gt;&lt;/a&gt; to enable super resolution, specifying the remote user's uid. Make sure the &lt;code&gt;AgoraSuperResolutionExtension&lt;/code&gt; framework is also included in your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Blurring 😶‍🌫️
&lt;/h3&gt;

&lt;p&gt;Virtual backgrounds was added to the SDK since &lt;code&gt;3.5.0&lt;/code&gt;, and now to further enrich that, there is an option to apply a blur to the background. Set the type of the custom background image to &lt;code&gt;AgoraVirtualBackgroundBlur&lt;/code&gt;, apply a &lt;code&gt;blur_degree&lt;/code&gt;, and the background will be blurred.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device Flash 📸
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;isCameraTorchSupported&lt;/code&gt; and &lt;code&gt;setCameraTorchOn&lt;/code&gt; have been added to the C++ APIs for both iOS and Android.&lt;/p&gt;

&lt;h3&gt;
  
  
  Media Stream Relay Pausing/Resuming ⏯
&lt;/h3&gt;

&lt;p&gt;Methods have been added to quickly pause and resume cross-channel media stream relays; as well as additional delegate methods to check if the pause and resume commands were successful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushing External Audio to a Specified Position 🎤
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pushExternalAudioFrameRawData&lt;/code&gt; and &lt;code&gt;pushExternalAudioFrameSampleBuffer&lt;/code&gt; have been updated to have an additional parameter. This parameter, &lt;code&gt;sourcePos&lt;/code&gt;, allows you to push the external audio frames to one of three positions: after audio capture, before audio encoding, or before local playback.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;code&gt;setExternalAudioSourceVolume&lt;/code&gt; has also been added; allowing you to set the volume of external audio frames at a specified position.&lt;/p&gt;

&lt;h3&gt;
  
  
  Music and Audio File Updates 🎶
&lt;/h3&gt;

&lt;p&gt;Playback speed, audio track, and channel mode of a music file can be set in code.&lt;/p&gt;

&lt;p&gt;This update deprecates &lt;code&gt;getAudioMixingDuration&lt;/code&gt; method in favour of &lt;code&gt;getAudioFileInfo&lt;/code&gt;. &lt;code&gt;getAudioFileInfo&lt;/code&gt; still gets information such as duration, but returns it in an AgoraRtcAudioFileInfo object in the &lt;code&gt;didRequestAudioFileInfo&lt;/code&gt; delegate method, found &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:didRequestAudioFileInfo:error:"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5G Quality Testing ⚡️
&lt;/h3&gt;

&lt;p&gt;When monitoring the connection quality of a device, a new property has been added to the &lt;code&gt;AgoraNetworkType&lt;/code&gt; enum, &lt;code&gt;AgoraNetworkTypeMobile5G(6)&lt;/code&gt;, or &lt;code&gt;.mobile5G&lt;/code&gt; with Swift, and &lt;code&gt;NETWORK_TYPE_MOBILE_5G&lt;/code&gt; on Android.&lt;/p&gt;




&lt;p&gt;These are just a handful of the new features to the SDK in v3.5.0. To see the full release notes for the native platform you’re using, check out one of the following links: &lt;a href="https://docs.agora.io/en/Video/release_ios_video?platform=iOS#v351"&gt;iOS&lt;/a&gt;, &lt;a href="https://docs.agora.io/en/Video/release_android_video?platform=Android#v351"&gt;Android&lt;/a&gt;, &lt;a href="https://docs.agora.io/en/Video/release_mac_video?platform=macOS#v351"&gt;macOS&lt;/a&gt;, and &lt;a href="https://docs.agora.io/en/Video/release_windows_video?platform=Windows#v351"&gt;Windows&lt;/a&gt;. Head to Agora’s &lt;a href="https://docs.agora.io/en/Video/downloads?platform=All%20Platforms"&gt;downloads page&lt;/a&gt; to get the latest SDK now.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>ios</category>
      <category>agora</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Build a Live Video Streaming iOS App with Agora 4.x Preview</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Fri, 17 Sep 2021 15:04:29 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/how-to-build-a-live-video-streaming-ios-app-with-agora-4-x-preview-3hfp</link>
      <guid>https://forem.com/maxxfrazer/how-to-build-a-live-video-streaming-ios-app-with-agora-4-x-preview-3hfp</guid>
      <description>&lt;p&gt;Recording and streaming a live feed from your app can be difficult, especially if you want to reach a global audience with little to no latency. The Agora Voice and Video SDK is perfect for reliably sending low-latency messages to a global audience, where you can have one or more people streaming their feeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you learn how to create an application that enables users to be either a streamer or an audience member in a session using Agora’s newest SDK version, &lt;code&gt;4.0.0&lt;/code&gt;. The setup is very similar to creating a video call with Agora, with a slight difference of roles: audience and broadcaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An Agora developer account (see &lt;a href="https://www.agora.io/en/blog/how-to-get-started-with-agora?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=live-broadcasting-app-agora-ios"&gt;How To Get Started with Agora&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Xcode 12.0 or later&lt;/li&gt;
&lt;li&gt;iOS device with iOS 13.0 or later (as this project uses SF Symbols)&lt;/li&gt;
&lt;li&gt;A basic understanding of iOS development&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: If you need to target an earlier version of iOS, you can change how the buttons are created in the provided&lt;/em&gt; &lt;a href="https://github.com/AgoraIO-Community/Agora-iOS-Swift-Example/tree/ng-sdk-update"&gt;&lt;em&gt;example project&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Let’s start with a new, single-view iOS project. Create the project in Xcode, and then add Agora’s 4.0.0 Preview Swift Package.&lt;/p&gt;

&lt;p&gt;Add the package by opening selecting &lt;code&gt;File &amp;gt; Swift Packages &amp;gt; Add Package Dependency&lt;/code&gt;, then paste in the link to this Swift Package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/agorabuilder/AgoraRtcEngine_iOS_Preview.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At the time post is written, the latest release is &lt;code&gt;4.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to jump ahead, you can find the full example project here on branch &lt;code&gt;ng-sdk-update&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/Agora-iOS-Swift-Example/tree/ng-sdk-update"&gt;https://github.com/AgoraIO-Community/Agora-iOS-Swift-Example/tree/ng-sdk-update&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the App
&lt;/h2&gt;

&lt;p&gt;In this initial UIViewController, we can just have a button in the center to join the channel. This button will tell the application to open a view on top, enabling us to join as an audience member by default.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The view in this example is very basic, as you can see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ru3qOOJw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AyWG0DFdt1TW1rwIV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ru3qOOJw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AyWG0DFdt1TW1rwIV.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create the ChannelViewController.swift file. This file will contain our UIViewController subclass called ChannelViewController, which displays the Agora pieces. In it, we will store values such as App ID, token, and channel name for connecting to the service, and add a button to change the user role between audience (default) and broadcaster. Also, initialise the Agora Engine with the correct client role. I have also preemptively added &lt;code&gt;remoteUserIDs&lt;/code&gt; and &lt;code&gt;userVideoLookup&lt;/code&gt;, which will keep track of the broadcasters/streamers.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When &lt;code&gt;userVideoLookup&lt;/code&gt; is set or updated, &lt;code&gt;reorganiseVideos&lt;/code&gt; is called to organise all of the streamed video feeds into a grid formation. Of course, a grid formation is not required, but if you want to implement the same thing, the &lt;code&gt;reorganiseVideos&lt;/code&gt; method is provided in the example project. Here’s a link to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgoraIO-Community/Agora-iOS-Swift-Example/blob/aebfd5c6fd6159c85906952acc9e1b2e9aaec117/Agora-iOS-Example/ChannelViewController%2BVideoControl.swift#L109"&gt;https://github.com/AgoraIO-Community/Agora-iOS-Swift-Example/blob/aebfd5c6fd6159c85906952acc9e1b2e9aaec117/Agora-iOS-Example/ChannelViewController%2BVideoControl.swift#L109&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hostButton has a target set as &lt;code&gt;toggleBroadcast&lt;/code&gt;. This method is found right at the bottom of the gist. As you can see, it toggles the value of &lt;code&gt;self.userRole&lt;/code&gt; between &lt;code&gt;.broadcaster&lt;/code&gt; and &lt;code&gt;.audience&lt;/code&gt;, and then sets the client role using &lt;code&gt;setClientRole&lt;/code&gt;. When the local client starts streaming, additional buttons should appear (for audio and video settings). But those buttons are displayed only after the client role has been changed, which is signaled by the delegate callback.&lt;/p&gt;

&lt;p&gt;In the gist above, the ChannelViewController is set as the &lt;code&gt;AgoraRtcEngineDelegate&lt;/code&gt;, so we should add that protocol to our class, along with some callback methods.&lt;/p&gt;

&lt;p&gt;The main callback methods we need for a basic streaming session are &lt;a href="https://docs.agora.io/en/voice/API%20Reference/oc/v2.8.0/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:didJoinedOfUid:elapsed:"&gt;didJoinedOfUid&lt;/a&gt;, &lt;a href="https://docs.agora.io/en/voice/API%20Reference/oc/v2.8.0/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:didOfflineOfUid:reason:"&gt;didOfflineOfUid&lt;/a&gt;, &lt;a href="https://docs.agora.io/en/voice/API%20Reference/oc/v2.8.0/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:didClientRoleChanged:newRole:"&gt;didClientRoleChanged&lt;/a&gt;, and &lt;a href="https://docs.agora.io/en/voice/API%20Reference/oc/v2.8.0/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:firstRemoteVideoDecodedOfUid:size:elapsed:"&gt;firstRemoteVideoDecodedOfUid&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;As mentioned in the gist, the &lt;a href="https://docs.agora.io/en/voice/API%20Reference/oc/v2.8.0/Protocols/AgoraRtcEngineDelegate.html#//api/name/rtcEngine:didClientRoleChanged:newRole:"&gt;didClientRoleChanged&lt;/a&gt; is the callback for when we have changed the local user role to broadcaster or audience member.&lt;/p&gt;

&lt;p&gt;If a remote host is streaming to us, the nonhosting view should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cMgk-Vsn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2ALhNI59wgEo469G7E.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cMgk-Vsn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2ALhNI59wgEo469G7E.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user is streaming, they should have additional options, including the ability to switch between the device’s front- and back-facing cameras and turning the camera or microphone on and off. In this case, a beautification toggle is added. To show and hide these options, the &lt;code&gt;isHidden&lt;/code&gt; property of the button container is set to false or true, respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pXCyo9n---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AMTb9t4aK5VlkDr4Y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pXCyo9n---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AMTb9t4aK5VlkDr4Y.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your application you may not want anyone to be able to start streaming. You could easily achieve this by requiring a password in the app (a static password or one authorised by a network request). Or you could offer separate apps for hosts and audience members.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Resources
&lt;/h2&gt;

&lt;p&gt;For more information about building applications using Agora.io SDKs, take a look at the &lt;a href="https://docs.agora.io/en/Video/start_call_ios?platform=iOS&amp;amp;utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=real-time-messaging-video-dynamic-channels"&gt;Agora Video Call Quickstart Guide&lt;/a&gt; and &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=real-time-messaging-video-dynamic-channels"&gt;Agora API Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also invite you to &lt;a href="https://www.agora.io/en/join-slack/"&gt;join the Agora.io Developer Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>agora</category>
      <category>video</category>
    </item>
    <item>
      <title>Multi-User Augmented Reality Experiences with Agora (Part 2 of 2)</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Fri, 20 Aug 2021 13:46:08 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/multi-user-augmented-reality-experiences-with-agora-part-2-of-2-2loe</link>
      <guid>https://forem.com/maxxfrazer/multi-user-augmented-reality-experiences-with-agora-part-2-of-2-2loe</guid>
      <description>&lt;p&gt;CollaboratAR is an iOS example project created by Agora to show some ways you can use Agora SDKs to create an interactive experience in augmented reality. This post shows you how to connect the dots using Agora real-time messaging, making the experience shareable between multiple people from anywhere in the world.&lt;/p&gt;

&lt;p&gt;To see an overview of how the example works, check out this post:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/maxxfrazer" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F450789%2Fe9b799a9-9703-4b82-88d9-d208ab97cba9.jpeg" alt="maxxfrazer"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/maxxfrazer/multi-user-collaborative-ios-ar-experiences-with-agora-part-1-of-2-3na1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Multi-User Collaborative iOS AR Experiences with Agora (Part 1 of 2)&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ Aug 20 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#swift&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#arkit&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#realitykit&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#realtime&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;And the full source code:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AgoraIO-Community" rel="noopener noreferrer"&gt;
        AgoraIO-Community
      &lt;/a&gt; / &lt;a href="https://github.com/AgoraIO-Community/Collaborative-AR-RTM" rel="noopener noreferrer"&gt;
        Collaborative-AR-RTM
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A collaborative Augmented Reality iOS experience using Real-time Messaging with RealityKit
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  What is Agora Real-time Messaging (RTM)?
&lt;/h1&gt;

&lt;p&gt;Agora real-time Messaging is an SDK that enables you to send data between devices that are connected to the network. These data could contain an encoded struct, plain text, or even larger files, ranging from PDF to 3D files.&lt;/p&gt;

&lt;p&gt;For example, one set of messages we will send across the network include our device location in the scene and details about the room we have created for others to be able to join it.&lt;/p&gt;

&lt;h1&gt;
  
  
  How Real-time Messaging is Used in CollaboratAR
&lt;/h1&gt;

&lt;p&gt;Currently, six different types of messages will be sent across the network in CollaboratAR. They are stored in an enum to make it easy to tell them apart when sending and receiving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// MARK: Globe Scene&lt;/span&gt;

  &lt;span class="c1"&gt;/// A session is available to display on the globe&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;channelAvailable&lt;/span&gt;
  &lt;span class="c1"&gt;/// Request all available sessions&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;getSessionData&lt;/span&gt;

  &lt;span class="c1"&gt;// MARK: Collaborative Session&lt;/span&gt;

  &lt;span class="c1"&gt;/// A model has been created or transform modified&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;singleCollabModel&lt;/span&gt;
  &lt;span class="c1"&gt;/// Multiple models are available to display in the scene&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;multiCollabModels&lt;/span&gt;
  &lt;span class="c1"&gt;/// A model has been deleted&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;removeCollabModel&lt;/span&gt;
  &lt;span class="c1"&gt;/// Transform update for a remote user&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;peerUpdate&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Globe Scene 🌎
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Sending
&lt;/h3&gt;

&lt;p&gt;In the initial scene a globe is visible. If a remote user is currently in a channel or experience, we see them on the globe as a red circle with an orange centre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95m64nbzzbhrrb1rkhdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95m64nbzzbhrrb1rkhdt.png" alt="vr-globe-hitpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let's see how the presence of a channel is sent from the hosting device.&lt;/p&gt;

&lt;p&gt;In our project, there are two methods called &lt;code&gt;sendCollabChannel&lt;/code&gt; that have two different uses: one sends data to the channel, and the other sends data to a specific user. The channel-wide message is sent when a new experience is created. The message is sent to a specific user when a new user joins the globe lobby.&lt;/p&gt;

&lt;p&gt;The data sent across starts in the form of a struct called &lt;code&gt;ChannelData&lt;/code&gt;, which contains a few key data:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ChannelData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;channelID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SIMD3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;ChannelData&lt;/code&gt; struct conforms to &lt;code&gt;Codable&lt;/code&gt;, which ensures we are able to encode all the data of our struct and provides methods to easily do so.&lt;/p&gt;

&lt;p&gt;To send this ChannelData struct across the network, we need to create a message that can be sent across the Agora RTM network. The class for this message will be &lt;code&gt;AgoraRtmRawMessage&lt;/code&gt;. The documentation for this class is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_oc/Classes/AgoraRtmRawMessage.html?platform=iOS" rel="noopener noreferrer"&gt;https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_oc/Classes/AgoraRtmRawMessage.html?platform=iOS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our data can be encoded with the Swift &lt;a href="https://developer.apple.com/documentation/foundation/jsonencoder" rel="noopener noreferrer"&gt;JSONEncoder&lt;/a&gt; like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// channelData: ChannelData&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Force unwrap here should be handled gracefully&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;jsonData&lt;/code&gt; object is of type &lt;a href="https://developer.apple.com/documentation/foundation/data" rel="noopener noreferrer"&gt;Data&lt;/a&gt;, which is ready to use in the creation of an &lt;a href="https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_oc/Classes/AgoraRtmRawMessage.html?platform=iOS" rel="noopener noreferrer"&gt;AgoraRtmRawMessage&lt;/a&gt; instance. The second property when creating an &lt;a href="https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_oc/Classes/AgoraRtmRawMessage.html?platform=iOS" rel="noopener noreferrer"&gt;AgoraRtmRawMessage&lt;/a&gt; is a description, which we will use to state the type of data in the message by the RawRTMMessageDataType's rawValue, which is a String.&lt;/p&gt;

&lt;p&gt;We are sending a message of type &lt;code&gt;RawRTMMessageDataType.channelAvailable&lt;/code&gt;, so our &lt;a href="https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_oc/Classes/AgoraRtmRawMessage.html?platform=iOS" rel="noopener noreferrer"&gt;AgoraRtmRawMessage&lt;/a&gt; creation will look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;channelDataRTM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmRawMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelAvailable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And to send that to our lobby channel, which is used for the globe scene, we can just do this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// rtmChannel: AgoraRtmChannel&lt;/span&gt;
&lt;span class="n"&gt;rtmChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channelDataRTM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Receiving
&lt;/h3&gt;

&lt;p&gt;Now that our message has been sent across the network, we need to use the Agora RTM Delegate methods to catch the incoming messages and interpret them as channels available to join.&lt;/p&gt;

&lt;p&gt;As this message is sent across the channel, we need to use the AgoraRtmChannelDelegate to catch that, as well as the AgoraRtmDelegate for when messages are sent directly to users.&lt;/p&gt;

&lt;p&gt;The same message may be received either channel-wide or directly to a user, so instead of handling them individually, we can catch both delegate callbacks and send the messages through one set of logic in another method, &lt;code&gt;handleIncomingMessage&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;CollaboratARDelegateEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmDelegate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmChannelDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messageReceived&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmMember&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;handleIncomingMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;rtmKit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;kit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmKit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messageReceived&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fromPeer&lt;/span&gt; &lt;span class="nv"&gt;peerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;handleIncomingMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;fileprivate&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleIncomingMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmMember&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle messages&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the project on GitHub there is a big switch case that handles all the different messages, but we will initially discuss only the &lt;code&gt;channelAvailable&lt;/code&gt; type we are interested in.&lt;/p&gt;

&lt;p&gt;First, to determine the type of message, we can create an instance of &lt;code&gt;RawRTMMessageDataType&lt;/code&gt; using the rawValue initialiser inside the handleIncomingMessage function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, if our messageType is &lt;code&gt;.channelAvailable&lt;/code&gt;, we can decode the message back into a ChannelData type:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelAvailable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;channelData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;ChannelData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// show channel data on globe...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In our example project, there is a method to take a &lt;code&gt;ChannelData&lt;/code&gt; object and spawn a point on the globe, called &lt;code&gt;spawnHitpoint&lt;/code&gt;. &lt;code&gt;spawnHitpoint&lt;/code&gt; takes the data stored inside &lt;code&gt;ChannelData&lt;/code&gt; to set the position of the target and the behaviour, when clicked.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ChannelData&lt;/code&gt; object contains the unique channelID to join for both the real-time messaging channel and the audio channel. Read on to see how that is joined and what happens after joining the collaboration scene.&lt;/p&gt;
&lt;h3&gt;
  
  
  Globe Scene Conclusion
&lt;/h3&gt;

&lt;p&gt;That's all the data that needs to be sent across Agora RTM network in the initial globe scene. All each device needs to know is what available channels there are and where to place them on the globe. No audio channels are joined when in the lobby. That comes in the next section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Collaboration Scene
&lt;/h2&gt;

&lt;p&gt;A lot more data are sent around in the collaboration scene, including the positions of all the other users, any models they add, move, and delete, as well as the data interpreted from the audio channel they are all members of.&lt;/p&gt;

&lt;p&gt;The first step is joining the scene. This is triggered by a call to a method called setState, which contains an enum of the following type:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;CollaborationState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;globe&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChannelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will just cover the &lt;code&gt;collab&lt;/code&gt; case, leaving &lt;code&gt;globe&lt;/code&gt; empty in the snippets:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;collabState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;globe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// handle joining globe scene&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;collabData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;// the following two lines handle the creation of entities&lt;/span&gt;
  &lt;span class="c1"&gt;// that are not shared across the network&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tearDownGlobe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchCollabScene&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Enable local microphone, then join Audio channel&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtcKit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enableLocalAudio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtcKit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collabData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtcID&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
     &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtcID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;
     &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updatePositions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Create + Join RTM Channel using collabData&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtmKit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;withId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collabData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CollaboratARDelegateEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;joinResponse&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;joinResponse&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channelErrorOk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not join collabs channel: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;joinResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kt"&gt;CollaboratARDelegateEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinedCollabChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the above snippet, rtcKit is our &lt;code&gt;AgoraRtcEngineKit&lt;/code&gt; instance for joining the audio channel, and rtmKit is our &lt;code&gt;AgoraRtmKit&lt;/code&gt; for joining the real-time messaging channels.&lt;/p&gt;

&lt;p&gt;The audio channel ID is taken from &lt;code&gt;collabData.channelID&lt;/code&gt;, the same as our real-time messaging channel. These can be different, but they are the same strings in this project example. The token handling for audio and RTM are not covered in this post or the example project. For testing, you can use a development project that does not require a token, create a temporary token, or generate the token from a token server.&lt;/p&gt;

&lt;p&gt;Once we have joined the RTM channel, the designated host of the channel will send information about all the objects that are currently in the scene to the new joiner.&lt;/p&gt;

&lt;p&gt;To do so, we use the &lt;code&gt;AgoraRtmChannelDelegate&lt;/code&gt; method, memberJoined. From here, we check that the new member's channelId is different from the lobby (the globe channel). And if we have been in the channel longer than anyone else, we gather all the data about entities in the scene and send that to the newest member.&lt;/p&gt;

&lt;p&gt;The data shared will be an array of type &lt;code&gt;[ModelData]&lt;/code&gt;. ModelData is defined here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// Unique ID for this model&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="c1"&gt;/// Name of the USDZ file to use&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;usdz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="c1"&gt;/// Scale of the entity&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SIMD3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Orientation of the entity&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;simd_float4&lt;/span&gt;
    &lt;span class="c1"&gt;/// Position of the entity&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SIMD3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Full Transform of the entity based on the scale, rotation and translation&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;simd_quatf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translation&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// Last user to move this entity&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;usdz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usdz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usdz&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtmID&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Sending All Models
&lt;/h3&gt;

&lt;p&gt;All the entities we want to share conform to a custom protocol &lt;code&gt;HasCollabModel&lt;/code&gt;. They are children of an entity we have a reference to, called &lt;code&gt;collabBase&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;collabChildren&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collabBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$0&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;HasCollabModel&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;allModelData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collabChildren&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The struct &lt;code&gt;ModelData&lt;/code&gt; conforms to Codable, which means that it and an array of &lt;code&gt;ModelData&lt;/code&gt; can be encoded with JSON as easily as seen earlier with &lt;code&gt;ChannelData&lt;/code&gt;, and then sent to our user the same way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// member: AgoraRtmMember&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allModelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rawMessge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmRawMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiCollabModels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtmKit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawMessge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;toPeer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Receiving All Models
&lt;/h3&gt;

&lt;p&gt;On the other end, the device that just joined the channel needs to receive these models and place them in the scene.&lt;/p&gt;

&lt;p&gt;We already have the RawRTMMessageDataType, as shown in the globe scene. We need to add a case to &lt;code&gt;handleIncomingMessage&lt;/code&gt; to catch when our messageType is &lt;code&gt;.multiCollabModels&lt;/code&gt; and then go on to add those models to the scene:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiCollabModels&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelDatas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelDatas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's take a look at what the &lt;code&gt;update&lt;/code&gt; method does:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;CollabModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createModelEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;First, it checks if an entity with the same ID already exists in the scene. If it does, then the CollabModel entity transform is updated with a very short animation. Otherwise, a new CollabModel is created based on the data contained inside &lt;code&gt;ModelData&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;createModelEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;collabMod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CollabModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collabBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collabMod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Updating User Positions
&lt;/h3&gt;

&lt;p&gt;All the remote augmented reality users will have their location relative to the ground square sent to the channel so that all the other users know where they are in the world. The called method is &lt;code&gt;CollaborationExpEntity.updatePositions&lt;/code&gt;. It is called on an interval of 0.3 seconds after you have joined a channel and set the ground square. The 0.3 seconds value is arbitrary and can be altered if a faster update is desired.&lt;/p&gt;

&lt;p&gt;The type of object sent over in this is of a custom struct, PeerData:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;PeerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rtmID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rtcID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;usdz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SIMD3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;simd_float4&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SIMD3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Material&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Material&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="nv"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;simd_quatf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translation&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The color and transform properties are created by using the other properties because &lt;code&gt;Material.Color&lt;/code&gt; and &lt;code&gt;Transform&lt;/code&gt; do not conform to &lt;code&gt;Codable&lt;/code&gt; themselves. The above struct contains all the data needed to distinguish between the different remote users.&lt;/p&gt;
&lt;h4&gt;
  
  
  Sending User Position
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;updatePositions&lt;/code&gt; method, we first get the cameraTransform and then get that transform relative to the collabBase, which is the entity set when joining the collab session.&lt;/p&gt;

&lt;p&gt;If we are running on an iOS device and not the simulator, we proceed to create a PeerData object, get the channel instance, and send our raw message with the description showing that it is of &lt;code&gt;.peerUpdate&lt;/code&gt; type.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updatePositions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;camTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arView&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cameraTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;localisedCam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collabBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;camTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Check that we are on iOS, and the cameraMode is .ar&lt;/span&gt;
      &lt;span class="cp"&gt;#if os(iOS) &amp;amp;&amp;amp; !targetEnvironment(simulator)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arView&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cameraMode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create PeerData Codable object&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;peerData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;PeerData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;rtmID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtmID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rtcID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtcID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;usdz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;localisedCam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;localisedCam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assignedColor&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Encode PeerData&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rawMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmRawMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peerUpdate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Send positional data to the entire channel&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="cp"&gt;#endif&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Receiving User Position
&lt;/h4&gt;

&lt;p&gt;Very similarly to receiving all the model updates in the previous example, we catch the case when our messageType is &lt;code&gt;.peerUpdate&lt;/code&gt;, decode it, and send it to a collaboration update function.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peerUpdate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;peerData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;PeerData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;peerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Inside the update function, we either create a new &lt;code&gt;PeerEntity&lt;/code&gt;, or update the transform of the existing one:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;peerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PeerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;peerData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtmID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;PeerEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;peerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPeerEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;peerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Updating a Model
&lt;/h3&gt;

&lt;p&gt;A similar update to &lt;code&gt;.multiCollabModels&lt;/code&gt; is &lt;code&gt;.singleCollabModel&lt;/code&gt;. This update is sent almost exactly the same way as &lt;code&gt;.multiCollabModels&lt;/code&gt;, but it is a single model rather than an array.&lt;/p&gt;

&lt;p&gt;We send this update instantly on creation of a model, and periodically if a model is selected and ready to be moved around the scene.&lt;/p&gt;

&lt;p&gt;When joining the scene, a scheduled timer is initially fired to execute the function &lt;code&gt;updatePositions()&lt;/code&gt;, which sends user location every 0.3 seconds (but could be more frequent if required). When a model is selected, updatePositions sees that &lt;code&gt;selectedBox&lt;/code&gt; is not null, so it triggers a function (&lt;code&gt;sendCollabData&lt;/code&gt;) that sends its position as an update to the channel:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendCollabData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;collabEnt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HasCollabModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collabEnt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rawMessge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmRawMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;singleCollabModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawMessge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The message is then received by &lt;code&gt;handleIncomingMessage&lt;/code&gt; on the other end and calls the same method as with &lt;code&gt;.multiCollabModels&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;singleCollabModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Deleting an Entity
&lt;/h3&gt;

&lt;p&gt;When deleting an entity, the enum &lt;code&gt;.removeCollabModel&lt;/code&gt; is used, and the individual model data is sent with the following message:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// model: CollabModel, collabChannel: AgoraRtmChannel&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rawMessge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AgoraRtmRawMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRTMMessageDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeCollabModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;collabChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawMessge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;On receiving the delete message, the model in question is located in the scene and is removed from its parent:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeCollabModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ModelData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;foundEntity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CollaborationExpEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Found the entity we want to delete, remove it from the scene&lt;/span&gt;
    &lt;span class="n"&gt;foundEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeFromParent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Testing
&lt;/h1&gt;

&lt;p&gt;All of the above methods can be found in the full example project:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AgoraIO-Community" rel="noopener noreferrer"&gt;
        AgoraIO-Community
      &lt;/a&gt; / &lt;a href="https://github.com/AgoraIO-Community/Collaborative-AR-RTM" rel="noopener noreferrer"&gt;
        Collaborative-AR-RTM
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A collaborative Augmented Reality iOS experience using Real-time Messaging with RealityKit
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Some of the methods contain additional code to aid the rest of the experience. The above explanations focus mainly on using the Agora Real-time Messaging SDK to enable remote collaboration.&lt;/p&gt;

&lt;p&gt;If anything is unclear, please feel free to send me a &lt;a href="https://twitter.com/maxxfrazer" rel="noopener noreferrer"&gt;message on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Other Resources &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;For more information about building applications using Agora Video and Audio Streaming SDKs, take a look at the &lt;a href="https://docs.agora.io/en/Video/start_call_ios?platform=iOS&amp;amp;utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=swiftpm" rel="noopener noreferrer"&gt;Agora Video Call Quickstart Guide&lt;/a&gt; and &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=swiftpm" rel="noopener noreferrer"&gt;Agora API Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also invite you to join the &lt;a href="https://www.agora.io/en/join-slack/" rel="noopener noreferrer"&gt;Agora Developer Slack community&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope you've enjoyed this post and the project that comes along with it. The idea of this project is to showcase how Agora Real-time Messaging SDK can be used to create interactive experiences with people around the world.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>augmentedreality</category>
      <category>ios</category>
      <category>agora</category>
    </item>
    <item>
      <title>Multi-User Collaborative iOS AR Experiences with Agora (Part 1 of 2)</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Fri, 20 Aug 2021 13:45:36 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/multi-user-collaborative-ios-ar-experiences-with-agora-part-1-of-2-3na1</link>
      <guid>https://forem.com/maxxfrazer/multi-user-collaborative-ios-ar-experiences-with-agora-part-1-of-2-3na1</guid>
      <description>&lt;p&gt;Agora is much more than a video streaming SDK. One other SDK available from Agora is the Real-time Messaging SDK.&lt;/p&gt;

&lt;p&gt;The Agora Real-time messaging SDK can be used to send chunks of data across the network, including encoded structs, files, and plain text.&lt;/p&gt;

&lt;p&gt;CollaboratAR is an example project where people can either join an active session or create a new one, located at a place of their choosing on a 3D globe floating in front of them. Once joined, users can add, modify, and remove objects from the scene, with updates sent to everyone else in the channel.&lt;/p&gt;

&lt;p&gt;When joining a session, the users also join an audio channel using the Agora Audio SDK, so they can hear each other as well as see each other's location.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cDl6nw2_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnts3wlecjbpfbwgby9h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cDl6nw2_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnts3wlecjbpfbwgby9h.gif" alt="macos ios transform example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what the session might look like from the macOS view, which does not have augmented reality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V-tzRujy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6utij86crd2cjh2g70b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V-tzRujy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6utij86crd2cjh2g70b.png" alt="macOS VR Overview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post shows you how to use the app to connect with other people around the world. If you want to see how the app is made, go to the follow-up blog post here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/maxxfrazer" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pnk5LhId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--LnCqIvrv--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/450789/e9b799a9-9703-4b82-88d9-d208ab97cba9.jpeg" alt="maxxfrazer"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/maxxfrazer/multi-user-augmented-reality-experiences-with-agora-part-2-of-2-2loe" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Multi-User Augmented Reality Experiences with Agora (Part 2 of 2)&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ Aug 20 ・ 11 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#swift&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#augmentedreality&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ios&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#agora&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  App Flow
&lt;/h2&gt;

&lt;p&gt;On first launching the app, the user is instructed to find a horizontal surface that the globe will spawn from. After finding a good horizontal surface, the globe is spawned. Any active channels will appear on the globe in the form of red and orange circular targets.&lt;/p&gt;

&lt;p&gt;The user can either select one of these targets to join a current room, or create a room by clicking elsewhere on the globe model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JuvitvL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uycjwxd6j3pmzyjtq28x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JuvitvL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uycjwxd6j3pmzyjtq28x.png" alt="macos vr globe view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O32CtH7u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om4lob5rxx61j0glx4lg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O32CtH7u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om4lob5rxx61j0glx4lg.png" alt="macos vr hitpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a user joins a channel, the following things will happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user will join an audio channel and can hear all other users who have their microphones connected.&lt;/li&gt;
&lt;li&gt;A collection of models appear at the bottom that the user can place in the scene.&lt;/li&gt;
&lt;li&gt;The locations of all remote users appear in the form of a translucent sphere and a visible microphone if their microphones are on.&lt;/li&gt;
&lt;li&gt;Any models that have already been placed into the scene will appear in the place set by other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R1rUt_8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2hajuip7fukc9zolk20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R1rUt_8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2hajuip7fukc9zolk20.png" alt="macos collab view closer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The user can tap on their model of choice and then tap on the scene to place the model anywhere they like. Any placements of objects will be immediately shared with everyone else in the channel at the relevant location of the scene for them.&lt;/p&gt;

&lt;p&gt;A user can select a model to delete, scale, rotate, or move it around the scene. These updates are sent across the network. On receiving the update, the local session will animate the object from the old transform to the new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.agora.io/en/Real-time-Messaging/product_rtm?platform=iOS"&gt;Agora Real-time Messaging SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.agora.io/en/Voice/landing-page?platform=iOS"&gt;Agora Audio SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/realitykit"&gt;RealityKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;To see how the sample app works, you can check out our in-depth blog post on how each part interacts with the Agora Real-time Messaging SDK as well as the Agora Audio SDK:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/maxxfrazer" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pnk5LhId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--LnCqIvrv--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/450789/e9b799a9-9703-4b82-88d9-d208ab97cba9.jpeg" alt="maxxfrazer"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/maxxfrazer/multi-user-augmented-reality-experiences-with-agora-part-2-of-2-2loe" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Multi-User Augmented Reality Experiences with Agora (Part 2 of 2)&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ Aug 20 ・ 11 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#swift&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#augmentedreality&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ios&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#agora&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;The full source code is available here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AgoraIO-Community"&gt;
        AgoraIO-Community
      &lt;/a&gt; / &lt;a href="https://github.com/AgoraIO-Community/Collaborative-AR-RTM"&gt;
        Collaborative-AR-RTM
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A collaborative Augmented Reality iOS experience using Real-time Messaging
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What You Can Do with This Technology
&lt;/h2&gt;

&lt;p&gt;Using the techniques outlined in this project, you could create experiences for people to share ideas in augmented reality or virtual reality, as well as in 2D. Anything that someone does on their own device anywhere in the world could easily be represented in another user's experience using the Agora Real-time messaging SDK.&lt;/p&gt;

&lt;p&gt;The same techniques could be used to make a multiplayer game, live interactive support, or something more basic, like a live chat feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Resources &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;For more information about building applications using the Agora Video and Audio Streaming SDKs, take a look at the &lt;a href="https://docs.agora.io/en/Video/start_call_ios?platform=iOS&amp;amp;utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=swiftpm"&gt;Agora Video Call Quickstart Guide&lt;/a&gt; and the &lt;a href="https://docs.agora.io/en/Video/API%20Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=swiftpm"&gt;Agora API Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also invite you to join the &lt;a href="https://www.agora.io/en/join-slack/"&gt;Agora Developer Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>arkit</category>
      <category>realitykit</category>
      <category>realtime</category>
    </item>
    <item>
      <title>Getting Started with RealityKit: Models</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Mon, 01 Feb 2021 09:48:34 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/getting-started-with-realitykit-models-120b</link>
      <guid>https://forem.com/maxxfrazer/getting-started-with-realitykit-models-120b</guid>
      <description>&lt;p&gt;There are two main ways of adding models to your RealityKit scene, importing from a USD (usd, usda, usdc, or usdz) or a Reality file, or creating a mesh using the MeshResource generators provided with Xcode. There are a few shapes that can be generated via the MeshResource generators, other shape must be imported via a USDZ model or experience file from Reality Composer.&lt;/p&gt;

&lt;p&gt;This post also includes examples of how to import USD files from remote URLs, and customise imported models.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Importing from USDZ

&lt;ul&gt;
&lt;li&gt;Loading with Entity Hierarchy&lt;/li&gt;
&lt;li&gt;Loading an AnchorEntity&lt;/li&gt;
&lt;li&gt;Loading a BodyTrackedEntity&lt;/li&gt;
&lt;li&gt;Loading a Flattened Model&lt;/li&gt;
&lt;li&gt;Loading a Remote USDZ File&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Generating Shapes&lt;/li&gt;
&lt;li&gt;
Customising Models

&lt;ul&gt;
&lt;li&gt;With Hierarchy&lt;/li&gt;
&lt;li&gt;Without Hierarchy&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Importing from USDZ &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;One of the more popular and exciting methods of importing a model to RealityKit would be importing it from a USD file. There are several ways to do this, and not knowing the subtle differences can lead you to miss parts of the imported models such as animations.&lt;/p&gt;

&lt;p&gt;All the import methods either require a name and an optional bundle (omit the bundle to use the app's main bundle), or require a url and a unique resource name. To see how to use each see &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244083-load"&gt;this link&lt;/a&gt; for passing a name and bundle, and &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244082-load"&gt;this link&lt;/a&gt; for the url and resource name methods. The rest of this post will only discuss the name + bundle examples for simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading with Entity Hierarchy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To maintain information bundled in the USD file such as animations, the best import methods are &lt;code&gt;Entity.load&lt;/code&gt; and &lt;code&gt;Entity.loadAsync&lt;/code&gt;. The difference between these two is that &lt;code&gt;load&lt;/code&gt; will block the thread it is called on until the file has been imported, while &lt;code&gt;loadAsync&lt;/code&gt; will use a &lt;a href="https://developer.apple.com/documentation/realitykit/loadrequest"&gt;&lt;code&gt;LoadRequest&lt;/code&gt;&lt;/a&gt; (where the Output is an Entity) to get the model imported while allowing you to continue on the current thread. &lt;br&gt;
Another difference between the two, is that &lt;code&gt;load&lt;/code&gt; can throw an error, while the error found in &lt;code&gt;loadAsync&lt;/code&gt; can be caught in the &lt;code&gt;LoadRequest&lt;/code&gt; slightly differently.&lt;/p&gt;
&lt;h3&gt;
  
  
  Examples:
&lt;/h3&gt;

&lt;p&gt;Simple loading of a USDZ file named &lt;code&gt;toy_robot_vintage.usdz&lt;/code&gt; synchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"toy_robot_vintage"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// failed to load entity&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// do something with entity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Loading a USDZ file named &lt;code&gt;toy_robot_vintage.usdz&lt;/code&gt; asynchronously:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"toy_robot_vintage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;receiveCompletion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;loadCompletion&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// Added this switch just as an example&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;loadCompletion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;loadErr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loadErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"entity loaded without errors"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;receiveValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// do something with entity&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Loading multiple USDZ files asynchronously, where the parameter in receiveValue is of type &lt;code&gt;[Entity]&lt;/code&gt;, and will only reach there once all models have been imported:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"toy_robot_vintage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gramophone"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fender_stratocaster"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;receiveCompletion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;loadCompletion&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// Add switch case for loadCompletion&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;receiveValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// Do something with returned entities array.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: You may notice that there is a small freeze  and when you add the entity to your scene, even when using loadAsync. The freeze is due to large meshes or images stored in the materials being loaded into memory, which must be executed on the main thread.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Loading an AnchorEntity &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In a very similar way to &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;loadAsync&lt;/code&gt;, &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244085-loadanchor"&gt;&lt;code&gt;loadAnchor&lt;/code&gt;&lt;/a&gt; can load your USD file in as an &lt;code&gt;AnchorEntity&lt;/code&gt;. This lets you place it directly in your scene without needing to make a separate anchor for it. Any animations in your USDZ file will also be available, as with &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;loadAsync&lt;/code&gt;.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAnchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"toy_robot_vintage"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// failed to load entity&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anchoring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AnchoringComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;minimumBounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;arview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAnchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this example, the AnchoringComponent is set to place the contents of the USDZ file to a horizontal plane categorised as the floor with a size of at least 1m x 1m. Only loadAnchor is shown here, but &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244087-loadanchorasync"&gt;&lt;code&gt;loadAnchorAsync&lt;/code&gt;&lt;/a&gt; works the same way as &lt;code&gt;loadAsync&lt;/code&gt;, it will just return an AnchorEntity instead of an Entity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Load a BodyTrackedEntity &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When you want to import a model that is rigged up to be animated by a real person, you instead want to use &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3255555-loadbodytracked"&gt;&lt;code&gt;loadBodyTracked&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3255557-loadbodytrackedasync"&gt;&lt;code&gt;loadBodyTrackedAsync&lt;/code&gt;&lt;/a&gt; to import a BodyTrackedEntity.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadBodyTracked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"robot"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// failed to load entity&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// do something with body tracked entity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For more information on tracking human bodies in AR, this session from WWDC 2019 is a great resource:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2019/607"&gt;https://developer.apple.com/videos/play/wwdc2019/607&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Loading a Flattened Model &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When you just want to import a Model from a USDZ file, removing entity hierarchy as well as animations, you can import using &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244091-loadmodel"&gt;&lt;code&gt;loadModel&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://developer.apple.com/documentation/realitykit/entity/3244093-loadmodelasync"&gt;&lt;code&gt;loadModelAsync&lt;/code&gt;&lt;/a&gt;. This will turn your 3d model of what would be various levels of nodes and children into a single Entity containing meshes and an array of all the materials.&lt;/p&gt;

&lt;p&gt;In my opinion, this difference can be confusing to someone who is new to RealityKit and wanting to import their model that has an animation from a USDZ file; as seen by various questions on Stack Overflow asking what happened to their USDZ animations when importing a file using these methods. However, once aware of this difference, it is very useful to not have all the overhead of a large hierarchy of entities that you may not need.&lt;/p&gt;
&lt;h2&gt;
  
  
  Loading a Remote USDZ File &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Currently there is no straightforward or builtin way to import USDZ files from remote URLs, but thankfully there are a few solutions, here's one from StackOverflow:&lt;/p&gt;


&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;h1&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Gn-iPj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
            &lt;a href="https://stackoverflow.com/questions/59209192/how-to-download-usdz-by-urlsession/59242131#59242131" rel="noopener noreferrer"&gt;
              &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: How to download USDZ by URLSession?
            &lt;/a&gt;
        &lt;/h1&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Dec  9 '19&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/59209192/how-to-download-usdz-by-urlsession/59242131#59242131" rel="noopener noreferrer"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y9mJpuJP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          2
        &lt;/div&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wif5Zq3z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;This problem is solved
let entity call on the main thread. &lt;/p&gt;

&lt;pre&gt;&lt;code&gt;let url = URL(string: "download usdz url")  
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!  
let destinationUrl = documentsUrl.appendingPathComponent(url!.lastPathComponent)  
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)  
var request = URLRequest(url: url!)  
request.httpMethod = "GET"  
let downloadTask = session.downloadTask(with:&lt;/code&gt;&lt;/pre&gt;…
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    
      &lt;a href="https://stackoverflow.com/questions/59209192/how-to-download-usdz-by-urlsession/59242131#59242131" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
    
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;I've also altered the solution into a synchronous function with an example usage at the bottom in this gist:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h1&gt;
  
  
  Generating Shapes  &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In its current state, the provided generators include Box (cuboid), Plane, Sphere or Text. Ironically, out of the three shapes in the RealityKit logo, only one of those (a sphere) can be produced without importing USDZ files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rw7oEv7k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f2nq2e25j2wsspi29xn4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rw7oEv7k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f2nq2e25j2wsspi29xn4.jpg" alt="Objects that can be made with RealityKit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the mesh initialisers can be found on this page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/realitykit/meshresource"&gt;https://developer.apple.com/documentation/realitykit/meshresource&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to add one of these shapes to your RealityKit scene would be to use the ModelEntity initialiser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;newModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ModelEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eBVslQpp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3cqu2ew14lg165vclfx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eBVslQpp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3cqu2ew14lg165vclfx3.png" alt="cube_without_material"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice here, no material was specified in the ModelEntity initialiser, which means this broken shader sort of look is applied to the cube shape. This is the case for all the other MeshResource initialisers.&lt;/p&gt;

&lt;p&gt;For in-depth information on how to create materials in RealityKit and a few tricks with them, see my previous post:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="https://maxxfrazer.medium.com/getting-started-with-realitykit-materials-f97ab406ac61" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r73gnmzZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/fit/c/96/96/1%2AL6TXP8CxR9RJNFa93oWGVw.jpeg" alt="Max Cobb"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://maxxfrazer.medium.com/getting-started-with-realitykit-materials-f97ab406ac61" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Getting Started with RealityKit: Materials | by Max Cobb | AR/VR Journey: Augmented &amp;amp; Virtual Reality Magazine&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ &lt;time&gt;Feb 25, 2021&lt;/time&gt; ・ 
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ze5yh_2q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/medium_icon-90d5232a5da2369849f285fa499c8005e750a788fdbf34f5844d5f2201aae736.svg" alt="Medium Logo"&gt;
        maxxfrazer.Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;h1&gt;
  
  
  Customising Models &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Once you bring a USDZ file in your scene there may be some customisation you want to do to it, such as changing a material, or actually moving around one part of the model.&lt;/p&gt;

&lt;p&gt;If you wanted to change the material of a model imported from a USDZ file, there are a couple of ways you can do so.&lt;/p&gt;

&lt;h2&gt;
  
  
  With Hierarchy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you import your model with anything other than &lt;code&gt;loadModel&lt;/code&gt; or &lt;code&gt;loadModelAsync&lt;/code&gt; you can replace the material of your choosing with ease.&lt;br&gt;
Take this file, &lt;code&gt;gramophone.usdz&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yEWIOXVV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/thpl11y20teo4tkpt43b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yEWIOXVV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/thpl11y20teo4tkpt43b.png" alt="xcode_gram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above image I've selected the record in the middle, and on the right we can see its name is &lt;code&gt;GramophoneRecord1&lt;/code&gt;, and if we select the materials tab we can see that it only has one material named &lt;code&gt;pxrUsdPreviewSurface1SG&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DUJnnvCK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fywgpgsgm57hgkakppj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUJnnvCK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fywgpgsgm57hgkakppj6.png" alt="xcode_gram2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is actually only one material in this whole USDZ file, however different parts of it are used on different meshes in the file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next all we need to do is find the Entity named &lt;code&gt;GramophoneRecord1&lt;/code&gt; and replace its material with our own green one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;gramRecord1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gramophoneModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GramophoneRecord1"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;HasModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;gramRecord1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;materials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isMetallic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_lO6TpDo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ywrshhe3fpwupjvww97w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_lO6TpDo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ywrshhe3fpwupjvww97w.jpg" alt="gram_green"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Original on the left, altered material on the right. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Without Hierarchy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you imported your model using &lt;code&gt;loadModel&lt;/code&gt; or &lt;code&gt;loadModelAsync&lt;/code&gt;, then as discussed earlier there is no hierarchy of entities, just the one ModelEntity consisting of a ModelComponent with a single mesh and a collection of materials. Take this file, &lt;code&gt;fender_stratocaster.usdz&lt;/code&gt;:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BAX_SHW3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIu3vFEZRiLCzjHJ0mHbgOA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BAX_SHW3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIu3vFEZRiLCzjHJ0mHbgOA.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The USDZ file itself consists of two meshes one for the stand and one for the guitar. Each of those meshes contain materials, the stand has one, and the guitar actually has 19, so once imported with &lt;code&gt;loadModel&lt;/code&gt; the ModelEntity will have one mesh and a list of 20 materials.&lt;/p&gt;

&lt;p&gt;Let’s replace the guitar body with a generated SimpleMaterial. It could take a bit of guess work to figure out which one in those 20 is that material, but by clicking on the part of the model that has the material you want to change Xcode highlights it in the side bar, see that it is the very first material in the guitar’s list of materials. It isn’t incredibly clear, but the first material when imported with &lt;code&gt;loadModel&lt;/code&gt; is actually that of the guitar stand, leaving the guitar body material the second in the list, index 1.&lt;/p&gt;

&lt;p&gt;After importing the model, simply running this I was able to change the guitar body to an electric blue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;modelAdjusted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;materials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isMetallic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--npPBeNPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1200/1%2Aooqvj5sIth4xVZG9Nf9Wwg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--npPBeNPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1200/1%2Aooqvj5sIth4xVZG9Nf9Wwg.jpeg" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Original on the left, altered material on the right&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The version without hierarchy leads to a bit less code, but doesn't allow for embedded animations, and can lead to issues if our USDZ file ever changes such that the material order differs. Whereas the version with hierarchy lets us find a specific part of the model, which would be preferred with a more complex model.&lt;/p&gt;

&lt;p&gt;I hope you found something useful in this post, give it a like if you did! 👏👏👏&lt;/p&gt;

&lt;p&gt;Send me a message on &lt;a href="https://twitter.com/maxxfrazer"&gt;twitter&lt;/a&gt; or leave a comment if there’s something I’ve missed out or if there’s something that isn’t working for you in RealityKit.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ios</category>
      <category>realitykit</category>
      <category>swift</category>
    </item>
    <item>
      <title>Getting Started with RealityKit: Materials</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Mon, 25 Jan 2021 13:34:20 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/getting-started-with-realitykit-materials-4ad4</link>
      <guid>https://forem.com/maxxfrazer/getting-started-with-realitykit-materials-4ad4</guid>
      <description>&lt;p&gt;Setting the right material to your mesh in RealityKit is a crucial part of setting the scene and making your models look just right, and there are a few tools at your disposal with RealityKit.&lt;/p&gt;

&lt;p&gt;I’ve already talked about the newest and most complex type, &lt;code&gt;VideoMaterial&lt;/code&gt; here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="https://maxxfrazer.medium.com/realitykit-videomaterials-66ad05f396f4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bWOTWo4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/fit/c/56/56/1%2AL6TXP8CxR9RJNFa93oWGVw.jpeg" alt="Max Cobb"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://maxxfrazer.medium.com/realitykit-videomaterials-66ad05f396f4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;RealityKit VideoMaterials: Add Video textures to your ARKit Scene | Medium&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ &lt;time&gt;Jul 25, 2020&lt;/time&gt; ・ 
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ze5yh_2q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/medium_icon-90d5232a5da2369849f285fa499c8005e750a788fdbf34f5844d5f2201aae736.svg" alt="Medium Logo"&gt;
        maxxfrazer.Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;But there are a few other types of materials you can set by code, and ways to tailor them for your needs that you may not already know, learn about all these other types and what you can do with them in this post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Basic Materials

&lt;ul&gt;
&lt;li&gt;UnlitMaterial&lt;/li&gt;
&lt;li&gt;SimpleMaterial&lt;/li&gt;
&lt;li&gt;OcclusionMaterial&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Materials with Textures

&lt;ul&gt;
&lt;li&gt;Material Transparency&lt;/li&gt;
&lt;li&gt;UIImage to TextureResource&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Basic Materials &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  UnlitMaterial &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;UnlitMaterial is the most simple of all of these, it only has properties baseColor and tintColor. If you’re coming from SceneKit, or many other rendering engines, baseColor is essentially the diffuse of the material. Here is how you may set the baseColor of an UnlitMaterial to UIColor.systemGreen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UnlitMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemGreen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// equivalent to&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UnlitMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemGreen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aRgh1M0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AdgWdWhyZzWCgKWE7hj3plg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aRgh1M0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AdgWdWhyZzWCgKWE7hj3plg.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sphere with green UnlitMaterial&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that without any interaction with the light this sphere looks like it could be just a flat circle.&lt;/p&gt;

&lt;p&gt;The other parameter, tintColor is also there, but is not very useful when applying a simple color as the baseColor, but for example you can do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UnlitMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tintColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cyan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wJ3LGQhz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ATlM6gIB8OvbZsmXrVMaC4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wJ3LGQhz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ATlM6gIB8OvbZsmXrVMaC4g.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sphere with a cyan UnlitMaterial&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can see that the color ends up being cyan, rather than white, or a combination of them both. If you try any colours that have no crossover, such as red and blue you will get a result color of black, rather than purple which may be expected. &lt;code&gt;tintColor&lt;/code&gt; is best left for when the &lt;code&gt;baseColor&lt;/code&gt; is a white image rather than a color.&lt;/p&gt;

&lt;h2&gt;
  
  
  SimpleMaterial  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Moving on to &lt;code&gt;SimpleMaterial&lt;/code&gt;, we can use the initialiser with no parameters and set the baseColor like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MaterialColorParameter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemGreen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QXeqJJlD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AAqk7nMSdeU9PC7DCvOMUYg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QXeqJJlD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AAqk7nMSdeU9PC7DCvOMUYg.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The subtle darker shading at the bottom right, and lighter at the top left shows it’s interacting with light&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two additional properties introduced with SimpleMaterial; &lt;code&gt;metallic&lt;/code&gt; and &lt;code&gt;roughness&lt;/code&gt;. By using the default initialiser those properties are set to &lt;code&gt;float(0.0)&lt;/code&gt; and &lt;code&gt;float(1.0)&lt;/code&gt; respectively, meaning that the material is not metallic and it has a rough coat. These properties can also be set to a texture, which will be covered below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: If you print out the properties here it actually says the opposite, that metallic is 1.0 and roughness is 0.0, but this is a bug, if you set the properties as such you will get a different visual result. I’ve opened a feedback to Apple&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s that same sphere with metallic set to 1.0, and roughness set to 0.0:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JL75lDl6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AcQ9HLu9bOHQtV7YScKZe0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JL75lDl6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AcQ9HLu9bOHQtV7YScKZe0g.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shiny green sphere&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  OcclusionMaterial &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;OcclusionMaterial is very different, instead of writing its own color or texture properties to the depth buffer it writes alpha, meaning that everything behind it in your RealityKit scene will not be rendered, and instead the camera feed or otherwise RealityKit environment background (grey in my examples) will be shown instead.&lt;/p&gt;

&lt;p&gt;Here is an example where I have positioned two spheres with a cube in front of each of them. One cube has a SimpleMaterial with baseColor of blue, and the other has the same sized cube with OcclusionMaterial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i_Ysj6bl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2APacQI4IKua5nztjDMigVWA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i_Ysj6bl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2APacQI4IKua5nztjDMigVWA.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how there is a bite taken out of the sphere on the right in place of where the cube should be. This can be used to place an OcclusionMaterial in place of a real world object, which can then occlude any rendered objects that are supposed to be behind them. This is what the scene understanding feature does, that automatically detects surfaces and objects to then occlude virtual objects behind them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Materials with Textures &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In many instances you will not want a simple color as the material of your object in the scene. You may want to add a texture, for example on a cube to give the look of a die rolling around your scene, or a map of Earth to have a spinning globe in your scene.&lt;/p&gt;

&lt;p&gt;For SimpleMaterial and UnlitMaterial we can set the baseColor to a texture in the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;unlitMat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UnlitMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;litMat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMaterial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;texParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;TextureResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"planet_earth"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;unlitMat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;litMat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6_Tg9tic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AR59L5Ssl_bPRpJrIX5l40g.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6_Tg9tic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AR59L5Ssl_bPRpJrIX5l40g.gif" alt="Two globes rotating"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The planet earth image I used can be found &lt;a href="http://shadedrelief.com/natural3/ne3_data/8192/textures/4_no_ice_clouds_mts_8k.jpg"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With SimpleMaterial we can also apply textures to metallic and roughness. When we apply a float value of 1.0 to either of those, that is equivalent to giving a completely white image, and similarly 0.0 is a black image.&lt;/p&gt;

&lt;p&gt;If we then want the oceans to be metallic, and the land to be non metallic, we can apply an image such as the following to the metallic property:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AI0kno_j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AEq5NQ50nGSJ0XHhjnZMMtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AI0kno_j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AEq5NQ50nGSJ0XHhjnZMMtw.png" alt="img"&gt;&lt;/a&gt;planet_earth_ocean.png&lt;/p&gt;

&lt;p&gt;All the ocean is set to white (1.0), and the land is black (0.0). If we also want to set the ocean to be smooth and the land to be rough, we can apply the inverted image to the roughness property:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7YAsTav7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A25pN989gPyT0ssYwvgkkpA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7YAsTav7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A25pN989gPyT0ssYwvgkkpA.png" alt="img"&gt;&lt;/a&gt;planet_earth_land.png&lt;/p&gt;

&lt;p&gt;Those can be applied like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;earthParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;TextureResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"planet_earth"&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;oceanParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;TextureResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"planet_earth_ocean"&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;landParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;TextureResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"planet_earth_land"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;earthParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metallic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oceanParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roughness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;landParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting globe shows reflective oceans and matt land:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pzRd4jw9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AT6jHR_F1V29_rJPlAhAJWA.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pzRd4jw9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AT6jHR_F1V29_rJPlAhAJWA.gif" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Material Transparency &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You may have trouble using a transparent image on a model in RealityKit, I believe this is to do with the &lt;a href="https://en.wikipedia.org/wiki/Blend_modes"&gt;blend mode&lt;/a&gt;applied to the model. When setting a &lt;a href="https://commons.wikimedia.org/wiki/File:Happy_smiley_face.png"&gt;charming smiley face&lt;/a&gt; image with transparent edges to the baseColor property of an UnlitMaterial, then setting that to a basic plane we get these black corners where the opacity is 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LP3Zvfy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A2v9gOKhfhtAFcFGzc-eYEg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LP3Zvfy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A2v9gOKhfhtAFcFGzc-eYEg.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, if we simply set the tintColor to white with an alpha component less than 1, we get the opacity back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tintColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NVa9_avW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Agpumf6NiFCmSzr7gmI5EZw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NVa9_avW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Agpumf6NiFCmSzr7gmI5EZw.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When trying the same with instead a SimpleMaterial there is one more issue, and that is that the transparency only works when the metallic property is set to 1.0, or &lt;code&gt;true&lt;/code&gt; when using the &lt;a href="https://developer.apple.com/documentation/realitykit/simplematerial/3254731-init"&gt;initialiser property isMetallic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This issue has been around since the first version of RealityKit (iOS 13) in one form or another, and I’m expecting it will be solved once we’re able to manually set the blend mode of models.&lt;/p&gt;

&lt;p&gt;Translucency with a plane colored material works as you might expect - just change the alpha component of the UIColor. However, due to a bug in RealityKit, if you ever update the mesh with either a coloured or textured material, you will need to replace the entire ModelComponent like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;newModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ModelEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generatePlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withAlphaComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;isMetallic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;newModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;materials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// next line breaks transluency&lt;/span&gt;
&lt;span class="n"&gt;newModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mesh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generatePlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// next line fixes it&lt;/span&gt;
&lt;span class="n"&gt;newModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ModelComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generatePlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;materials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UIImage to TextureResource &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There is currently no way (that I’m aware of) to convert a UIImage to a TextureResource, which is a huge blow if you want to download images to your applications and set them to models in the scene. I’m hoping that this too will be fixed one way or another in future versions of RealityKit.&lt;/p&gt;




&lt;p&gt;Thanks to &lt;a href="https://medium.com/u/24ccbfcc28c9"&gt;Ryan Kopinsky&lt;/a&gt; for suggesting I write about some of these things when we were discussing them. I hope many RealityKit developers find this a useful resource when creating materials for Augmented Reality scenes!&lt;/p&gt;

&lt;p&gt;As usual, give this article some claps if you can, and feel free to message me on &lt;a href="http://twitter.com/maxxfrazer"&gt;Twitter&lt;/a&gt; or leave a response here if you have any questions.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>augmentedreality</category>
      <category>realitykit</category>
    </item>
    <item>
      <title>Getting started with RealityKit</title>
      <dc:creator>Max Cobb</dc:creator>
      <pubDate>Sun, 24 Jan 2021 09:53:50 +0000</pubDate>
      <link>https://forem.com/maxxfrazer/getting-started-with-realitykit-80o</link>
      <guid>https://forem.com/maxxfrazer/getting-started-with-realitykit-80o</guid>
      <description>&lt;p&gt;Before WWDC19, myself and other Augmented Reality (AR) enthusiasts were anticipating many updates to SceneKit to come along with some shiny new ARKit features; however this was not the case.&lt;/p&gt;

&lt;p&gt;Instead Apple decided to create a whole new framework to implement AR, and dubbed this new framework &lt;a href="https://developer.apple.com/documentation/realitykit"&gt;RealityKit&lt;/a&gt;. Here we have a whole new rendering engine, made from the ground up with AR not just in mind, but as the top priority. Although there may be some features missing; with this as v1.0 I have high hopes that this will become the standard for high-performance AR. Due to the name I also believe it could be intended for use with both Augmented and Virtual Reality, but I have yet to see any evidence for this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vFix8kU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qyqmiawumjwq8pkhw07e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vFix8kU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qyqmiawumjwq8pkhw07e.png" alt="Craig Federighi and his fantastic hair introducing RealityKit at WWDC19, source: 9to5mac.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started looking into RealityKit right from the start, however there’s currently limited materials out there helping people to find their feet, both if someone who is new to AR or is coming from other AR Frameworks, such as SceneKit. This collection of articles aims to get you started with understanding the new format of creating AR content programmatically with RealityKit, with new articles coming out as I use the features.&lt;/p&gt;

&lt;h2&gt;
  
  
  2020
&lt;/h2&gt;

&lt;p&gt;To see what was changed with RealityKit during WWDC 2020, refer to this story:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="https://maxxfrazer.medium.com/wwdc20-whats-new-in-arkit-and-realitykit-186c1b4dc1e9" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bWOTWo4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/fit/c/56/56/1%2AL6TXP8CxR9RJNFa93oWGVw.jpeg" alt="Max Cobb"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://maxxfrazer.medium.com/wwdc20-whats-new-in-arkit-and-realitykit-186c1b4dc1e9" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;WWDC20: What’s new in ARKit and RealityKit? | by Max Cobb | Medium&lt;/h2&gt;
      &lt;h3&gt;Max Cobb ・ &lt;time&gt;Jun 28, 2020&lt;/time&gt; ・ 
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ze5yh_2q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/medium_icon-90d5232a5da2369849f285fa499c8005e750a788fdbf34f5844d5f2201aae736.svg" alt="Medium Logo"&gt;
        maxxfrazer.Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The Collection
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-component-entity-bc59acb60728"&gt;Component Entity System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-touch-gestures-1ecddb9f9e15"&gt;Touch Gestures, including EntityGestures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-arcoachingoverlayview-80159d140c"&gt;ARCoaching, with ARCoachingOverlayView&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-events-97964fa5b5c7"&gt;Subscribing to Events, such as Collisions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/ar-session-recording-47baea680caf"&gt;Automating AR Sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-synchronization-289ba9409a6e"&gt;Multipeer Collaborative AR Sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-videomaterials-66ad05f396f4"&gt;Video Materials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-debugmodelcomponent-ca50a2ea64c9"&gt;Debug Component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/power-up-realitykit-by-adding-these-missing-features-71fa55c4ad5d"&gt;Useful Packages for Levelling-up RealityKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/maxxfrazer/getting-started-with-realitykit-materials-4ad4"&gt;More Materials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;More articles coming soon&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see code examples without support material; take a look at this repository below. In this repo I’m adding the new features together into a game similar to one that Apple showed off at WWDC19, for which they have yet to release the code for.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/maxxfrazer"&gt;
        maxxfrazer
      &lt;/a&gt; / &lt;a href="https://github.com/maxxfrazer/RealityKit-CardFlip"&gt;
        RealityKit-CardFlip
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An example app using Apple's new RealityKit framework
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
RealityKit CardFlip&lt;/h1&gt;
&lt;p&gt;I've been using RealityKit a lot since it was released, and am writing Medium posts about different aspects of it as well as adding examples to this app.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://medium.com/@maxxfrazer/getting-started-with-realitykit-3b401d6f6f" rel="nofollow"&gt;For specific details of what's covered, check out my Medium&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/maxxfrazer/RealityKit-CardFlipmedia/CardFlip_Example1.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wiJFPPSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/maxxfrazer/RealityKit-CardFlipmedia/CardFlip_Example1.gif" alt="CardFlip Example 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example code for Apple's new AR framework RealityKit, demonstrating the following things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@maxxfrazer/realitykit-component-entity-bc59acb60728" rel="nofollow"&gt;The Entity Component System&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Inheritance/Protocol Oriented&lt;/li&gt;
&lt;li&gt;AnchoringComponent (with Targets)&lt;/li&gt;
&lt;li&gt;ModelComponent&lt;/li&gt;
&lt;li&gt;CollisionComponent&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-touch-gestures-1ecddb9f9e15" rel="nofollow"&gt;HitTests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Animations&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@maxxfrazer/realitykit-arcoachingoverlayview-80159d140c" rel="nofollow"&gt;AR Coaching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
TODO:&lt;/h3&gt;
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Add game feedback
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Score counter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Audio Playback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following the RealityKit examples from this keynote, as the code used has not yet been released:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2019/605/" rel="nofollow"&gt;Building Apps with RealityKit&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
Wishlist for RealityKit:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Particles&lt;/li&gt;
&lt;li&gt;Custom Mesh Generation&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/maxxfrazer/RealityKit-CardFlip"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;p&gt;If you’ve enjoyed this article and want to see more in this RealityKit series follow me for updates or check out my &lt;a href="https://twitter.com/maxxfrazer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>realitykit</category>
      <category>augmentedreality</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
