Streaming is available in most browsers,
and in the Developer app.
-
Customize spatial Persona templates in SharePlay
Learn how to use custom spatial Persona templates in your visionOS SharePlay experience to fine-tune the placement of Personas relative to your app. We'll show you how to adopt custom spatial Persona templates in a sample app with SharePlay, move participants between seats, and test your changes in Simulator. We'll also share best practices for designing custom spatial templates that will make your experience shine.
Chapters
- 0:00 - Introduction
- 1:01 - SharePlay on visionOS
- 4:50 - Build "Guess Together"
- 23:41 - Play Guess Together
- 25:13 - Design for Spatial Personas
Resources
Related Videos
WWDC23
WWDC22
WWDC21
-
Download
- Hello, I'm Ethan - And I'm Kevin. And we're engineers on the Spatial FaceTime team. We're excited to walk you through the process of building a visionOS app that really takes advantage of the feeling of presence offered by spatial Personas. Along the way, you'll learn about new Xcode features and GroupActivities API you can use while developing your SharePlay app. This session is broken into three parts. First, I'll provide an introduction to FaceTime and SharePlay on visionOS. Then I'll walk you through the process of building "Guess Together", a sample app that demonstrates new API for defining custom spatial Persona templates. I’ll also show you how you can test your visionOS SharePlay apps in the simulator using new support for simulated FaceTime calls. Finally, Kevin will explain how to design great SharePlay experiences for spatial Personas. Let's start with FaceTime and SharePlay on visionOS.
FaceTime on visionOS takes advantage of spatial Personas to create a true sense of presence. FaceTime with spatial Personas really feels like you’re in the room together. SharePlay apps, like the Freeform app shown in this video, are a core part of this experience. By adopting SharePlay, you can extend the shared context provided by FaceTime to your app.
When you adopt SharePlay on visionOS, you’re responsible for keeping your app visually consistent. Your app’s UI should be synchronized between all participants on the call. FaceTime, in turn, is responsible for maintaining the shared spatial context by placing participants around your app in a consistent way. This visual and spatial consistency combine to create the illusion that participants are using a single copy of your app in a shared space. They should be able to communicate by pointing and gesturing at your app’s UI. The same way you might in a shared physical space when gathering around a whiteboard. A typical visionOS FaceTime call with spatial Personas begins with them arranged in a circle – allowing participants to easily interact with one another. But, at any moment, a participant can start a SharePlay activity and the call will transition into a spatial template – re-arranging each participant’s spatial Persona relative to the newly shared app. Importantly, this template is just defining the starting point or seat of each participant. At any moment, a participant or two or three could begin moving around the space – and break out of this initial starting template. However, if a participant recenters using the digital crown on their device – they’ll be placed back in their starting seat.
The GroupActivities framework provides a variety of built-in system spatial templates that a SharePlay app can adopt. For example, a video app is likely to prefer the template we’ve been using so far: the side-by-side template where participants are placed along a curved line with the app in front of them. But, a Music app might be enjoyed from the conversational template where participants are arranged in an open circle with a gap for the app on the edge of that circle. While a 3D modeling app would likely be viewed from the surround template where participants are arranged in a closed circle around a shared volume.
All of these templates define seats – relative to the shared app – where spatial Personas will be placed at the start of the shared activity, regardless of where they may have been in the shared space prior. But what if your activity isn’t best served by any of these built-in system templates? For example, maybe you’re working on a chess app and you’d like to have two players seated across from each-other with the remaining participants arranged as spectators to the side. Or maybe you’re building a card-game where the dealer should be placed across from the other players. Well, new in visionOS 2 – you can create custom spatial templates, giving your app complete control over the placement of spatial Personas relative to your shared scene. I’m really excited about custom spatial templates and the possibilities they unlock for unique social experiences on visionOS.
The next part of this session will be focused on more advanced SharePlay concepts. If you are new to SharePlay, Group Activities or visionOS, I’d recommend first watching “Build custom experiences with Group Activities" and “Build spatial SharePlay experiences” to get the most out of this session. The former will explain how to synchronize your app's state between activity participants while the latter provides an overview of spatial SharePlay.
With that – let’s take a tour of the sample app I’ve been working on: “Guess Together”.
“Guess Together” is a team-based phrase guessing game played in FaceTime with spatial Personas. It’s a bit like charades. You’ll break into two teams and then take turns. During your turn you’ll be given a secret phrase, like the name of a celebrity or an idiom, and then attempt to have your team guess that phrase using any means necessary – as long as you don’t say the phrase itself. Guess Together is defined by four stages. When you first launch the app, a welcome UI is presented that invites you to create a SharePlay group session with your current FaceTime call. Once SharePlay begins, Guess Together enters the category selection stage. Here you’ll decide what categories you want to play with. For example, maybe you want to play with phrases pulled from historical events, or with something more simple, like different fruits and vegetables. Next, it’s time for team selection where you’ll decide to join the blue or red team. And finally, it's time to play. Guess together will present a view with a scoreboard and a timer. A second view will appear in front of the active player with the secret phrase their team-mates will need to guess. Now, with the exception of the welcome stage – which occurs before SharePlay begins, each of these stages requires a spatial template. In the same way you carefully consider how to implement the user interface for each screen in your app, a SharePlay app on visionOS requires careful consideration about how to arrange spatial Personas relative to your shared scene.
The category selection screen presents a shared user-interface that all participants should be able to easily see and interact with. At this point, no participants are distinct in any way – there isn’t an active player and players haven’t been divided into teams. For these reasons, the right choice here is the system-provided side-by-side template – which is the default for a windowed app like Guess Together. This template is designed exactly for this purpose and will arrange participants in a curved line, facing the shared app – providing everyone easy access to the interface. The team selection screen has similar requirements, but with a key difference – at this point, participants will be divided into separate teams. And I think it would be really nice if the app placed you in a seat next to your team mates. Because of this need for team-based seats, the team selection screen should use a custom spatial template.
To begin, participants will sit in one of these five seats facing the app, similar to side-by-side. But then once they join a team, Guess Together will assign them to a blue team or red team seat. FaceTime will actually re-seat them in a new seat and they’ll find themselves sitting next to the other members of their team.
The game stage is the most obviously in need of a custom spatial template. There are multiple roles participants can take on here from the current player to the players on the opposing team.
The active player will be seated to the left of the scoreboard window with a podium placed in front of them. Their team-mates should be directly across from them – to the right of the shared app. And the opposing team will then sit directly in front of the scoreboard window.
And that’s Guess Together! We’ll spend the next part of this session implementing each of those templates together. This is going to require a bit of iteration and testing of the SharePlay activity itself. Fortunately, Xcode 16 makes this a great experience.
New, in Xcode 16, you can create simulated FaceTime calls on the visionOS simulator. This is a game changer for developing SharePlay apps for visionOS, allowing you to fully build out your app without ever making a real FaceTime call on a device.
To start a simulated FaceTime call, first open the Features menu in the menu bar. Then the FaceTime submenu. From there, select the configuration of remote participants you'd like. It's important to try your app out with a variety of configurations.
And that's it. Let's try it out.
Here’s the Guess Together project open in Xcode. I'll run it on the simulator.
And here's Guess Together! Since I'm not currently in a SharePlay session, it’s presenting the welcome stage.
I'll activate FaceTime by moving my mouse to the Features menu, the FaceTime submenu, and then I'll select User and 4 Spatial Participants.
And just like that, I'm in a simulated FaceTime call. I'll use the spatial button to activate my spatial Persona.
Now the Play Guess Together button is active and I'm ready to start SharePlay. I'll click on it.
The app is now in a SharePlay session and presenting the category selection stage. If I pan to the left – I can see that the simulated participants have moved into the side-by-side spatial template.
Let's take a look at how that's configured in the code.
Guess Together manages its interactions with the current SharePlay group session in a session controller class. I'll open it now.
The SessionController is in charge of tracking and synchronizing all of the app’s state during a given SharePlay session. It stores the session itself, a GroupSessionMessenger used to synchronize state with other participants, and the session's SystemCoordinator used to configure spatial Personas. It then provides methods for updating the various pieces of state in the app, like entering the team selection stage, joining a team and starting a game. There’s a lot done here already so let’s take stock of what’s remaining.
First, we need to design and configure the custom spatial template template for the team-selection stage. Once that’s done, we’ll move onto the game stage and put together its template. Then, we’ll configure a group immersive space for the game stage where we’ll place a podium with the current phrase in front of the active player’s seat.
Finally, once we’re done with the app, we’ll join a FaceTime call and actually play Guess Together, so you can understand how the whole experience comes together.
As a reminder, this is where we’re heading with the team selection template. Five audience seats in front of the app, and two sets of three team-based seats.
Let’s get started by building a template with just those five starting seats. Spatial templates are defined by creating a type that conforms to the SpatialTemplate protocol. I'll create a struct called TeamSelectionTemplate and conform it to SpatialTemplate.
The primary requirement of a SpatialTemplate is an array of template elements.
These elements are defined as seats with a given position.
A seat can hold a single spatial Persona. Now I just need to position the seats.
Seat positions are defined relative to the position of the shared app. I want to place my first seat directly in front of the app, so I’ll set its X offset to 0, aligning it with the app’s center. I’ll then set its Z offset to 4, placing it 4 meters in front of the app. I’ll keep the second seat’s Z offset the same, but this time I’ll set its X offset to 1, placing it a meter to the right of the first seat.
I'll begin by positioning my seat at the app, and then offset it 4 meters in front of the app via the Z axis. For the second seat, I'll start by placing it in the same position, but I'll adjust the X offset by 1 meter to the right.
I'll do the same for the third, but this time offsetting 1 meter to the left. And I’ll place the two remaining seats 1 additional meter to the left and right.
We’ve now defined the team selection template by creating a struct that conforms to the SpatialTemplate protocol. Each seat in the template is defined by providing a position that is offset from the app by a given value in both the X and Z axes. These values are provided using meters. This is a great start but we still need seats for the red and blue teams. So we need to set 6 additional seats along an angle from these original 5. And then we need to mark them as reserved for members of the blue and red teams respectively. I'll begin by creating a seat that is offset half a meter to the left and half a meter forward from the template's left-most seat.
I'll continue that pattern for the 2 remaining blue team seats. But now I need a way to mark these seats as reserved for blue team. This is where SpatialTemplate roles come in. By attaching a role to a seat, you can reserve it for participants who have assigned themselves that role.
Roles are defined by creating a type that conforms to the SpatialTemplateRole protocol. So, I’ll create a Role enum with String raw values that conforms to SpatialTemplateRole. And then I'll create blueTeam and redTeam roles.
Great. Now, I can assign the blueTeam Role to those blue team seats and reserve them for members of blueTeam.
So, for the blueTeam seats, we created a blueTeam Role with an enum that conforms to SpatialTemplateRole. We then placed 3 seats at an angle from the template’s starting seats and assigned each of them that role. This will reserve them for participants on the blue team.
Let's do the same thing for the red team seats.
And that's the completed template! Now we need to activate it during the team selection stage. I’ll do that in the SessionController.
Guess Together manages its spatial templates with this updateSpatialTemplatePreference method. I'll update the teamSelection case access the SystemCoordinator configuration.
Instead of setting a built-in preference, I’ll use the new custom method and provide the TeamSelectionTemplate.
We’ve now created and configured the custom template, but there's an important step we still need to take: role assignment. We need to tell FaceTime when the local participant’s team has changed so that the system knows to move them to their new seat.
Guess Together is designed to handle role assignment in this updateLocalParticipantRole method. I'll begin by switching over the current game stage since the role I need will be dependent on it.
In the teamSelection stage, the local participants spatial template role is dependent on their team. So I'll switch over that. Now we're ready to assign the blue team role. I'll access the systemCoordinator and use the assignRole method.
I'll pass in the blueTeam Role we defined earlier.
I'll follow the same pattern for the red team.
Now if I my team is nil, I should be in the audience seats, which don’t have a role. So I can just use the resignRole() method on the systemCoordinator. Now is a good opportunity to update my roll for the categorySelection stage. Category selection is using the built-in side-by-side template that doesn’t have any roles. I want to be sure to resign my role when entering that stage in case I’m still holding onto a role from a prior stage.
Great. Let's try it out.
I'll run the app again and this time we'll enter team selection.
When I join red team, I'll move over to the red team seats.
And when I join blue team, now I'm with blue team.
Quick review. When you add a roll to a spatial template seat, you're reserving that seat for participants with that roll. FaceTime will keep that seat empty until a participant assigns themselves the associated roll. To assign the local participant a roll, you use the assign roll method on the system coordinator. If there's a seat available with that roll, FaceTime will move the local participant's spatial persona to that seat. To return the local participant to a seat without a roll, you can give up the local participant's roll by calling the resign roll method on the system coordinator.
Alright, that's the first task done. These last two should go a little more quickly. Let's move on to the spatial template for the game stage.
Here's where we're heading with the game template. I'd like to have the active player sit across from their teammates. As participants take turns, they should cycle in and out of the hot seat. The players on the opposite team should then sit in audience seats off to the side. So far, there's nothing new here from what we use to create the team selection template. But there's one additional customization I'd like to make to this template. Seat Direction. By default, spatial template seats will face towards the shared app, but you can actually customize the direction of a seat to look at any given point. For this template, I'd like to have the active player looking at the active team. and have the active team looking at the active player. For the audience seats, it makes sense to keep the default of looking directly at the app. I'll open the game template now. I've already completed most of this template, so we don't need to worry about the details here. We'll just work on setting each seat's direction. The template defines three sets of seats. The player seat, the active team seats, and the audience seats.
Notice that I defined the active team position as its own variable when creating this template. This allows me to reuse that position when setting the direction of the player seat. So I'll set the direction of the player seat to look at that position by providing a direction parameter and using the looking app method.
The orientation of the player seat will now face towards the center of the active team seats. Next, let's set the direction of the team seats. I'll again provide a direction parameter. This time passing the player seat directly to the looking app method.
Alright, that's the completed template.
Now we need to activate it during the game stage and update Guess Together’s role assignment logic. Let's go back to the session controller.
I'll start by configuring the game template using the same pattern we used for team selection.
Now it's time for role assignment. In this case, the role depends on who the active player is. I'll create a conditional with three branches.
The first for the active player, the second for the active team, and the third for the audience. We'll use the system coordinator again and the assigned role method.
We'll do the same thing for the active team.
Finally, for the audience members, I'll just resign the current role.
Nice. Let's try out the game stage.
When the stage starts, I'm looking towards the seats where my teammates will sit. The scoreboard is to my left, and the audience is to my right. This will create a great experience for people playing guests together, since they won't need to re-orient themselves every time they become the active player. You might be wondering why none of the simulated participants are sitting in the team seats across the room. This is because the simulated participants will never assign themselves a role in the template. So they'll always be seated in the first available seats without roles.
And that's task two done. Let's take a look at the game stages immersive space.
During the game stage, we need a way to show the current secret phrase to the active player. Now, I could use a second private window here that's shown only to the active player, but I think it would be nice to open an immersive space so that I can maintain this shared spatial context.
This will allow me to place an additional piece of shared UI that's positioned in front of the active player.
By default, when you open an immersive space while in SharePlay, that space will be private to each participant. This means that participants won't be able to see each other spatial personas while they're in the immersive space. If you're building an immersive space that should be shared among participants, you can opt-in to a group immersive space on the system coordinator configuration. And when developing a custom spatial template for a group immersive space, you can actually position UI elements relative to seats in your template, since the origin of your template is also the origin of the shared space. Let's try it out.
I'll start by opting into a shared group immersive space. I'll access the system coordinator configuration and set the supports group immersive space property to true.
Now we're ready to add the secret phrase podium. I've already started working on it. Let's check it out. I'll expand my immersive space group and open the phrase deck podium view.
The podium view is defined as a reality view with a SwiftUI attachment view. The podium's position is set in this update podium position method. Let's position it in front of the active player seat. I'll start by moving the podium to match the position of the player seat as it's defined in the game template. And then I'll offset it by half a meter or so in front of the active player. Notice that I can use the position of the active player seat directly here. Both RealityKit and GroupActivities use meters as their default length unit, so no conversion is required. And since we opted into a group immersive space, the origin of the apps immersive space is also the origin of its spatial template.
Let's try it out.
Now as soon as the active player enters the game stage, they're facing towards their team members and with the clue podium directly in front of them.
I can't wait to try this out. And that's it.
All that's left is to actually play guests together. I'll FaceTime you from the other room.
Hey there, Kevin. Hey Ethan. I brought Gabby and Mia with me. Hi. Hey there. Hey. Let's play guests together. What category should we play? Hmm. I think I'll turn off vegetables. Good idea. I'll turn off film and television too.
All right. Are we ready to pick teams? Let's do it.
I'll join the blue team.
I'm going to choose the red team.
I'm also joining the red team.
Guess that means I'm team blue.
Sounds good. Everyone ready? Let's play.
Looks like I'm up first. Good luck. Here we go.
All right. This is a fruit, typically red or green. Pretty sweet, pretty popular, relatively small. Oh, an apple. Yes. Okay. This is a musical instrument. Its name is actually an abbreviation for its full name, which means quiet loud. What are you talking about? It has 88 keys. A piano.
Yes! Haha.
That was fun. Nice job, everyone.
Thanks, Ethan, for building such a great sample app. Guess together is so much fun to play with friends.
Now that we've seen how to build an app that uses custom spatial templates, let's review the process of designing SharePlay experiences for spatial personas. The first step of designing a new SharePlay experience for visionOS is selecting the type of spatial persona template you're going to use. Specifically, deciding between a custom template or a built-in system template.
Now, the first question you should ask yourself is, will my experience be improved by a custom template? Before deciding to create a fully custom spatial persona template, you should be confident that your experience will really benefit from doing so. The system spatial persona templates have been extensively tuned to create great experiences with spatial personas.
The side-by-side template is recommended when you want all the participants to be seated in front of your app, which is really useful for windowed apps and media viewing activities. The conversational template keeps the focus of the participants on each other, who keeps your app close by.
And the surround template is great for when your app is a volume and you want participants to be placed in a circle around it. There are several benefits to using the system templates over custom ones. First, they provide a familiar and likely more comfortable experience for people using your app. Just as we'd recommend you use standard UI controls in your apps interface were possible, it's a good idea to use spatial persona templates that people are already familiar with.
Furthermore, system templates automatically adapt to the number of spatial personas in a call, even when participants leave or join during the activity. And there will always be enough seats up to the maximum supported by FaceTime to ensure that everyone can see each other. System templates also ensure the app is usable by everyone, by dynamically adapting to the size of your shared scene.
Assuming you have evaluated all of the system templates and still think your experience will benefit from a fully custom one, the next question you should ask yourself is, could my experience benefit from using multiple templates? I'd encourage you not to treat your experience as a monolith. When designing guests together, instead of picking a single custom template for the entire app, we consider each stage of the game separately and use the mix of system provided and custom templates. You may find that your answers to these questions are different for various stages of your activity, so I'd encourage you to use different templates for different parts of your experience, where appropriate. Now before going any further, there's another question you should ask yourself.
How will my experience support participants who are not using spatial personas? When designing a SharePlay experience with visionOS in mind, you may accidentally only consider the participants that are using their spatial personas in your SharePlay activity. But that may not be enough.
If you're building a multi-platform SharePlay activity, your experience may have participants who are on platforms without support for spatial personas. And even if your app is exclusively for visionOS, participants are able to toggle their spatial persona at any time during your SharePlay activity. Keep both of these cases in consideration when designing your experience. So that's template selection. You're now ready to move on to defining your seats. There are several key best practices you should always follow when defining seats. You should include a seat for every spatial persona. You should provide enough distance between seats, and you should sequence your seats with careful consideration and intention.
Let's say I'm building a template for a two-player board game, like chess. My initial template draft might look like this. I've placed two seats across each other with the app's volume in between them. This will work fine as long as only two spatial personas join my SharePlay activity. But it will make for a poor experience for a third participant who joins and wants to use their spatial persona. Because I haven't created an additional seat for them, FaceTime will be unable to place their persona with the other participants. As a fallback, FaceTime will place the third participant in a built-in template on their own, but it will mean that the two people playing chess will be unable to see the third, and the third person will be unable to see the first two.
This is why it's important to include a seat for every spatial persona. If I add three additional seats to my template, I now have enough seats for the maximum spatial personas that FaceTime supports in a call, and I've created a better user experience for everyone in my activity. Equally important to providing a seat for every spatial persona is including enough space between each persona's seat. We recommend placing seats at least one meter apart from each other. This gives enough room for people to adjust in their seat slightly without feeling like they're crowding their neighbors.
It's also important to keep in mind that when personas are too close to each other, they will disappear and be replaced with static contact photos. By ensuring your seats have enough space between them, you can be confident that your activity's participants will always be able to see each other's spatial personas comfortably.
Finally, it's important to sequence your seats with intention. I'd recommend checking your template with every possible combination of spatial personas and ensure that it fills correctly. For example, if there are three spatial personas when your custom template is set, the first three seats you specify will be used in order.
This example will fill the seats from the center out as new participants join.
If instead you defined your seats from the left to right, they will be filled in that order, which might make your template feel unbalanced in the case that not all the seats are occupied.
You can define the order that your seats should be filled in with the order of the seats provided in your template's elements array. Here's a simple template that places seats in a line facing towards the app. Guess together uses something similar for team selection. Notice that the order of the seats in the elements list is also the order that participants will fill them as they join the call.
That wraps up defining seats for your custom spatial template. But before moving on, there's another really important question you need to ask yourself. Where do you expect people to be looking when they are initially placed in each seat? In other words, where is each seat's focal point? By default, seats will face towards the center of the app but you have full control over seat direction. People using your app will need your help to know what their focal point should be when you place them in that seat. Should they be looking at the app at another participant? Somewhere else entirely. Our new API provides a number of tools to help you orient your seats appropriately. In Guess Together's game template, we use the looking app method to point the active player seat towards their teammates. Looking at can also be provided any spatial template element position if you want to have the seat face somewhere else specifically. You can also use the aligned with method to align a seat within our orthogonal axes of the app, such as the X or Z axis. For example, if you wanted to place a row of seats in a line, all facing directly forwards perpendicular to your app's plane, you'd likely want to align with the Z axis. Finally, after you create an initial seat direction, you can always rotate it by a given value in both degrees or radiance.
After considering the direction and position of each of your seats, it's time to consider which seats in your template are special. In other words, do any of your seats need to be reserved? Instead of asking yourself which seats need roles, consider instead which seats are special or need a reservation. With the chest template we looked at earlier, I want to make sure that the player's seats remain empty unless they're filled by the active players for each team. Even if one or both of the players does not have their spatial persona enabled, their seats should remain empty and be reserved for them should they need it later.
The spectator seats, on the other hand, are not special and should not have roles assigned to them. You might be tempted to create a third role here, just for spectators. But this might create a worse user experience as it will introduce delays while each participant requests the spectator role instead of allowing FaceTime to seat them directly into a seat without a role. To review, rely on seats without roles whenever possible, so that FaceTime can place participants in seats without delay.
Use roles as a way to reserve seats for participants who need to be in a specific location. And keep in mind that not all participants will be able to acquire a role as they may be on a different platform or have their spatial persona disabled.
You've now considered what type of template to use, how to position and orient your seats, and if your seat should be reserved for specific participants. Now you should have a great custom spatial template for your SharePlay experience, but there's just one last design tenant I'd like you to consider.
Avoid surprises. When designing your SharePlay app for spatial personas, one of the most important things you can do is ensure that your spatial persona templates are never unexpected for people using your app. When using a custom spatial template for your app, you have a lot of power. With template transitions and role changes, your app can move a participant's spatial persona. The representation in the FaceTime call around the shared space. It's really important that you consider how people will react anytime you change templates or use roles to assign them to a new seat. Let's review some general guidelines.
Minimize template transitions.
Changing templates by moving or rotating seats, swapping roles, or switching between template types can be very disorienting for people, so it's best to do so as infrequently as possible. In Guess Together, each stage uses a single template where the seats remain in the same place, but uses roles to move participants between seats only when necessary.
Provide visual context clues to help participants orient themselves whenever you do transition between templates. This might just come down to seat direction and making sure that each seat has a visual anchor point in its field of view, which may be the app itself or another participant.
Finally, try to tie template changes to explicit participant actions. A good example of this is the team selection stage in Guess Together, where role changes are tied to somebody pressing the button to join blue team.
Or join red team.
You should consider how to best avoid bad surprises at every stage of your design process to make the good surprises truly delightful.
To wrap up, I'd recommend downloading the guest together sample code and trying it out with a friend. It's a lot of fun and will give you a great idea of what's possible with spatial templates. Use the new simulated FaceTime calls in Xcode 16's visionOS Simulator. Test out your SharePlay experience. This is an amazing tool that'll help you develop a great SharePlay app for visionOS, regardless of what kind of spatial template your app uses.
Consider if your experience is improved by a custom spatial template, and also where would benefit from intentional use of the existing system templates. Finally, as you embark on designing great SharePlay experiences for visionOS, remember to avoid bad surprises. Custom spatial templates are a powerful tool that enable really exciting experiences in FaceTime, but they require a lot of careful consideration.
We can't wait to see the experiences you build with custom spatial templates on visionOS. Thank you so much.
-
-
12:32 - Initial team selection template
// Team selection template – custom spatial template import GroupActivities struct TeamSelectionTemplate: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 4)), .seat(position: .app.offsetBy(x: 1, z: 4)), .seat(position: .app.offsetBy(x: -1, z: 4)), .seat(position: .app.offsetBy(x: 2, z: 4)), .seat(position: .app.offsetBy(x: -2, z: 4)), ] }
-
13:31 - Completed team selection template with seat roles
import GroupActivities /// The custom spatial template used to arrange Spatial Personas /// during Guess Together's team-selection stage. /// /// The team selection template contains three sets of seats: /// /// 1. Five audience seats that participants are initially placed in. /// 2. Three Blue Team seats that participants are moved to /// when they join team Blue. /// 3. Three Red Team seats. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// % $ /// % $ /// Blue Team % $ Red Team /// * * * * * /// /// Audience /// ``` struct TeamSelectionTemplate: SpatialTemplate { enum Role: String, SpatialTemplateRole { case blueTeam case redTeam } let elements: [any SpatialTemplateElement] = [ // Blue team: .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: Role.blueTeam), .seat(position: .app.offsetBy(x: -3.0, z: 3.0), role: Role.blueTeam), .seat(position: .app.offsetBy(x: -3.5, z: 2.5), role: Role.blueTeam), // Starting positions: .seat(position: .app.offsetBy(x: 0, z: 4)), .seat(position: .app.offsetBy(x: 1, z: 4)), .seat(position: .app.offsetBy(x: -1, z: 4)), .seat(position: .app.offsetBy(x: 2, z: 4)), .seat(position: .app.offsetBy(x: -2, z: 4)), // Red team: .seat(position: .app.offsetBy(x: 2.5, z: 3.5), role: Role.redTeam), .seat(position: .app.offsetBy(x: 3.0, z: 3.0), role: Role.redTeam), .seat(position: .app.offsetBy(x: 3.5, z: 2.5), role: Role.redTeam) ] }
-
14:59 - Configuring a custom spatial template
systemCoordinator.configuration.spatialTemplatePreference = .custom(TeamSelectionTemplate())
-
15:39 - Assigning the local participant a spatial template role
systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam)
-
16:00 - Resigning the local participant from a spatial template role
systemCoordinator.resignRole()
-
17:00 - Spatial template roles
// Associating a role with a seat .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: TeamSelectionTemplate.Role.blueTeam) // Assigning the local participant a role systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam) // Resigning the local participant from their current role systemCoordinator.resignRole()
-
18:42 - Game template with seat direction
import GroupActivities /// The custom spatial template used to arrange spatial Personas /// during Guess Together's game stage. /// /// The team selection template contains three sets of seats: /// /// 1. An seat to the left of the app window for the active player. /// 2. Two seats to the right of the app window for the active player's /// teammates. /// 3. Five seats in front of the app window for the inactive team-members /// and any audience members. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// Active Player % $ Active Team /// $ /// /// * * * * * /// /// Audience /// /// ``` struct GameTemplate: SpatialTemplate { enum Role: String, SpatialTemplateRole { case player case activeTeam } var elements: [any SpatialTemplateElement] { let activeTeamCenterPosition = SpatialTemplateElementPosition.app.offsetBy(x: 2, z: 3) let playerSeat = SpatialTemplateSeatElement( position: .app.offsetBy(x: -2, z: 3), direction: .lookingAt(activeTeamCenterPosition), role: Role.player ) let activeTeamSeats: [any SpatialTemplateElement] = [ .seat( position: activeTeamCenterPosition.offsetBy(x: 0, z: -0.5), direction: .lookingAt(playerSeat), role: Role.activeTeam ), .seat( position: activeTeamCenterPosition.offsetBy(x: 0, z: 0.5), direction: .lookingAt(playerSeat), role: Role.activeTeam ) ] let audienceSeats: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 5)), .seat(position: .app.offsetBy(x: 1, z: 5)), .seat(position: .app.offsetBy(x: -1, z: 5)), .seat(position: .app.offsetBy(x: 2, z: 5)), .seat(position: .app.offsetBy(x: -2, z: 5)) ] return audienceSeats + [playerSeat] + activeTeamSeats } }
-
21:41 - Configure group immersive space
// Configure group immersive space for await session in GuessingActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } systemCoordinator.configuration.supportsGroupImmersiveSpace = true }
-
30:35 - SimpleLine Template
// SimpleLine.swift struct SimpleLine: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 2)), .seat(position: .app.offsetBy(x: 1, z: 2)), .seat(position: .app.offsetBy(x: -1, z: 2)), .seat(position: .app.offsetBy(x: 2, z: 2)), .seat(position: .app.offsetBy(x: -2, z: 2)) ] }
-
31:35 - lookingAt Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) )
-
31:46 - alignedWith Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) )
-
32:02 - rotatedBy Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) ) // Rotate by a given angle .seat( position: teamSeatPosition, direction: .lookingAt(.app).rotatedBy(.degrees(30)) )
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.