{"$schema": "https://raw.githubusercontent.com/guitaripod/wwdc-sessions/master/schema/transcript.schema.json", "id": "wwdc2026-344", "language": "eng", "source": "https://developer.apple.com/videos/play/wwdc2026/344", "wordCount": 3532, "segments": [{"start": 7.0, "text": "Hi, I'm Justin, an engineer on the Swift Intelligence Frameworks team."}, {"start": 11.0, "text": "Welcome to this code-along."}, {"start": 13.0, "text": "In this video, I'll take an app and make it available to Siri, step by step."}, {"start": 18.0, "text": "Imagine I start a conversation with Siri on my iPhone and ask:"}, {"start": 21.0, "text": "\"Who's coming to my picnic?\""}, {"start": 23.0, "text": "Siri searches for the event, its attendees, and shows me the guest list."}, {"start": 29.0, "text": "I realize it includes that one friend that's always late, so I say:"}, {"start": 33.0, "text": "\"Move it to noon… actually 11:30 am\"."}, {"start": 40.0, "text": "Siri asks to confirm the details, and I say:"}, {"start": 42.0, "text": "\"sounds good, thanks\"."}, {"start": 48.0, "text": "Siri makes the changes and renders a custom view."}, {"start": 52.0, "text": "Now I need to update my friends:"}, {"start": 54.0, "text": "\"Text everyone going to the picnic and let them know about the change using emojis\"."}, {"start": 60.0, "text": "After double-checking the message, I say:"}, {"start": 62.0, "text": "\"Send it\"."}, {"start": 66.0, "text": "And just like that, Siri sends the message."}, {"start": 69.0, "text": "Then I remember the event's note had a reminder"}, {"start": 71.0, "text": "to bring something important to the picnic."}, {"start": 73.0, "text": "So I ask:"}, {"start": 74.0, "text": "\"What did I need to bring to the picnic again?\""}, {"start": 78.0, "text": "Siri searches the event and finds a note to myself to bring a chocolate cake."}, {"start": 82.0, "text": "I'm curious how much time I have to get ready, so I say:"}, {"start": 85.0, "text": "\"How long will it take to drive there?\""}, {"start": 90.0, "text": "Siri finds the event's location, and gives me an estimate."}, {"start": 94.0, "text": "This is how Siri can make everyday things effortless… simply with a conversation."}, {"start": 99.0, "text": "That's the mission for today."}, {"start": 101.0, "text": "I'll take an existing app and make it available to Siri."}, {"start": 105.0, "text": "Siri is powered by Apple Intelligence, Apple's personal intelligence system."}, {"start": 109.0, "text": "Developers integrate their apps with Apple Intelligence"}, {"start": 112.0, "text": "through the App Intents framework,"}, {"start": 114.0, "text": "so their app's content and actions become available within Siri"}, {"start": 117.0, "text": "and other system experiences."}, {"start": 119.0, "text": "The companion video \"Build intelligent Siri experiences with App Schemas\""}, {"start": 123.0, "text": "covers the concepts behind the framework"}, {"start": 125.0, "text": "and how Apple Intelligence and Siri work with your app's content and actions."}, {"start": 130.0, "text": "If App Intents is new to you,"}, {"start": 132.0, "text": "the video \"Get to know App Intents\" covers the core fundamentals"}, {"start": 135.0, "text": "like intents, entities, and queries,"}, {"start": 137.0, "text": "and how they connect to system features like Siri and Shortcuts."}, {"start": 141.0, "text": "I've been working on a sample project called CometCal."}, {"start": 144.0, "text": "It's a SwiftUI calendar app with a fun cosmic twist,"}, {"start": 148.0, "text": "and the source code is available for download on the Apple Developer website"}, {"start": 151.0, "text": "for you to follow along."}, {"start": 153.0, "text": "It does all the basics of a calendar app: showing today's events,"}, {"start": 157.0, "text": "viewing and editing them,"}, {"start": 159.0, "text": "creating more,"}, {"start": 161.0, "text": "and even managing different calendars."}, {"start": 165.0, "text": "Right now, the only way to interact with it though is through the screen…"}, {"start": 169.0, "text": "but that's about to change!"}, {"start": 171.0, "text": "In this video, I'm going to update CometCal"}, {"start": 173.0, "text": "so that Siri can: understand the app's content and answer my questions."}, {"start": 177.0, "text": "And take actions like updating an event… making Siri more helpful than ever."}, {"start": 183.0, "text": "The first step is giving Siri an understanding of my app's content."}, {"start": 187.0, "text": "Right now, Siri has no idea what a calendar"}, {"start": 189.0, "text": "or an event means inside CometCal."}, {"start": 192.0, "text": "That's where App Schemas come in."}, {"start": 193.0, "text": "App Schemas describe my app's content"}, {"start": 196.0, "text": "and actions in terms Siri can already understand."}, {"start": 199.0, "text": "They define the structure of my entities, the parameters of my actions,"}, {"start": 202.0, "text": "and the outputs."}, {"start": 204.0, "text": "No training phrases, no natural language processing on my end."}, {"start": 208.0, "text": "App Schemas are organized into App Schema Domains."}, {"start": 212.0, "text": "The calendar domain covers everything related to scheduling:"}, {"start": 215.0, "text": "events, calendars, attendees,"}, {"start": 218.0, "text": "and the actions that operate on them."}, {"start": 220.0, "text": "Now that the groundwork is set… time to suit up, jump into Xcode,"}, {"start": 224.0, "text": "and start the countdown!"}, {"start": 226.0, "text": "CometCal already has a CalendarModel in its data layer,"}, {"start": 229.0, "text": "that's the SwiftData model for calendars,"}, {"start": 231.0, "text": "like the difference between a Personal calendar and a Work calendar."}, {"start": 235.0, "text": "The goal is to create an app entity that represents it using App Schemas,"}, {"start": 239.0, "text": "so Siri can understand what a calendar is in this app."}, {"start": 242.0, "text": "I'll create a new Swift file called CalendarEntity, and import AppIntents."}, {"start": 251.0, "text": "Next, I'll type calendar_ in the editor."}, {"start": 255.0, "text": "Xcode offers every schema in the Calendar domain, right in autocomplete."}, {"start": 259.0, "text": "Since the goal is a calendar entity, I'll select calendar_calendar."}, {"start": 265.0, "text": "The snippet fills in the structure:"}, {"start": 267.0, "text": "the @AppEntity macro, properties, DisplayRepresentation and query stubs."}, {"start": 272.0, "text": "This is now a schematized entity, a type Siri can reason over."}, {"start": 276.0, "text": "A few things to fill in."}, {"start": 278.0, "text": "First, I'll set the id type to UUID to match the data model."}, {"start": 284.0, "text": "To enable matching by meaning and not just text,"}, {"start": 286.0, "text": "I'll conform to the IndexedEntity protocol."}, {"start": 289.0, "text": "Conforming to IndexedEntity allows my app"}, {"start": 292.0, "text": "to donate entities using the Spotlight index"}, {"start": 294.0, "text": "to get the benefits of semantic understanding."}, {"start": 297.0, "text": "When an entity is donated, Siri can resolve it by name,"}, {"start": 300.0, "text": "by property, or by context,"}, {"start": 302.0, "text": "without requiring a custom property query."}, {"start": 306.0, "text": "To keep things moving, I added ways to convert"}, {"start": 308.0, "text": "between the data model and the entity."}, {"start": 311.0, "text": "The CalendarEntity's initializer maps from CalendarModel"}, {"start": 314.0, "text": "to pull out what the entity needs."}, {"start": 316.0, "text": "And the convenience .entity property on CalendarModel"}, {"start": 319.0, "text": "produces a CalendarEntity using the initializer."}, {"start": 323.0, "text": "I'll use this soon in places like the query to convert between the two."}, {"start": 328.0, "text": "Next, I'll add a @Dependency property for the CalendarManager in the query."}, {"start": 332.0, "text": "That's CometCal's data layer that handles all the SwiftData operations."}, {"start": 337.0, "text": "The @Dependency property wrapper"}, {"start": 338.0, "text": "is how App Intents injects shared resources into intents and queries,"}, {"start": 342.0, "text": "so instead of creating new instances, it provides the same object I register once"}, {"start": 346.0, "text": "as I've done on the right."}, {"start": 349.0, "text": "The CalendarManager is main-actor isolated,"}, {"start": 352.0, "text": "so I'll also annotate the query struct as @MainActor."}, {"start": 355.0, "text": "I'll implement the method required by the EntityQuery protocol."}, {"start": 359.0, "text": "This fetches calendars by ID using the CalendarManager dependency."}, {"start": 364.0, "text": "Now, EntityQuery covers cases"}, {"start": 366.0, "text": "where the system already knows an entity's ID."}, {"start": 369.0, "text": "But later, when creating events,"}, {"start": 371.0, "text": "the system will need to know which calendars are available"}, {"start": 373.0, "text": "so Siri can offer them as options."}, {"start": 376.0, "text": "For that, I'll conform to EnumerableEntityQuery"}, {"start": 379.0, "text": "and add an allEntities method that returns all calendars."}, {"start": 384.0, "text": "For the DisplayRepresentation,"}, {"start": 386.0, "text": "I'll set the title to the calendar title"}, {"start": 388.0, "text": "and the image to a system image of a calendar."}, {"start": 391.0, "text": "Siri and Spotlight use this when displaying the entities."}, {"start": 395.0, "text": "That's one entity ready for orbit… almost."}, {"start": 398.0, "text": "There's one more piece that's easy to miss."}, {"start": 400.0, "text": "IndexedEntity defines the shape of my indexed content,"}, {"start": 403.0, "text": "but entities still need to be donated."}, {"start": 406.0, "text": "To do that, I'll open the CalendarManager file."}, {"start": 409.0, "text": "Anytime calendars, or any indexed entity for that matter, are changed,"}, {"start": 413.0, "text": "the index needs to be updated."}, {"start": 415.0, "text": "For that, I have a CSSearchableIndex instance"}, {"start": 418.0, "text": "initialized in the CalendarManager's initializer"}, {"start": 421.0, "text": "that uses a unique name for CometCal."}, {"start": 424.0, "text": "In the createCalendar method, just before returning the new calendar,"}, {"start": 427.0, "text": "I'll donate the entity by calling indexAppEntities"}, {"start": 430.0, "text": "using the searchable index instance from before."}, {"start": 434.0, "text": "Similarly, in the updateCalendar method, I'll index the updated calendar entity."}, {"start": 441.0, "text": "And in the deleteCalendar method,"}, {"start": 443.0, "text": "I'll remove the entity from the index by calling deleteAppEntities,"}, {"start": 447.0, "text": "passing in the entity's id and type."}, {"start": 452.0, "text": "I'll fire up the engines and give it a go…"}, {"start": 454.0, "text": "I'll open CometCal and create a new calendar called \"Lunar Orbit Log\""}, {"start": 463.0, "text": "Then, I'll swipe down to search for \"Lunar Orbit Log\"…"}, {"start": 469.0, "text": "and there it is with the calendar icon and the title."}, {"start": 472.0, "text": "With CalendarEntity working, the next two entities follow the same pattern,"}, {"start": 476.0, "text": "but each introduces something new and shiny."}, {"start": 479.0, "text": "Here's a new file called AttendeeEntity with AppIntents already imported."}, {"start": 483.0, "text": "Same pattern as before…"}, {"start": 485.0, "text": "type calendar_attendee and select the snippet."}, {"start": 489.0, "text": "The familiar parts are the same…"}, {"start": 491.0, "text": "All of that is wired up off-camera so I can keep moving at light speed."}, {"start": 494.0, "text": "Feel free to pause here and check the completed source code."}, {"start": 497.0, "text": "Unlike CalendarEntity, AttendeeEntity conforms"}, {"start": 500.0, "text": "to the TransientAppEntity protocol instead of IndexedEntity."}, {"start": 504.0, "text": "That's intentional."}, {"start": 506.0, "text": "A transient app entity is one that represents a temporary entity"}, {"start": 509.0, "text": "that doesn't require a unique identifier and isn't meant to be queried."}, {"start": 513.0, "text": "That's the right fit here."}, {"start": 515.0, "text": "In CometCal, an attendee represents a person's participation in a specific event,"}, {"start": 519.0, "text": "not the person themselves."}, {"start": 521.0, "text": "The same person can attend multiple events,"}, {"start": 523.0, "text": "and indexing each attendance separately"}, {"start": 525.0, "text": "would create duplicative results in Spotlight."}, {"start": 528.0, "text": "Since attendees are always accessed through the event that holds them,"}, {"start": 531.0, "text": "there's no need for an independent look up path."}, {"start": 534.0, "text": "TransientAppEntity makes that explicit… no query to write,"}, {"start": 538.0, "text": "no index to maintain."}, {"start": 540.0, "text": "Attendees have properties that are required by the schema"}, {"start": 542.0, "text": "like the boolean property to tell whether this attendance is optional."}, {"start": 547.0, "text": "One new item is the IntentPerson type…"}, {"start": 550.0, "text": "the system's standard way to represent a person"}, {"start": 552.0, "text": "with a name and contact information."}, {"start": 555.0, "text": "This is useful when sharing this data between apps,"}, {"start": 558.0, "text": "like sending an attendee's email address to draft a message in the Mail app."}, {"start": 562.0, "text": "The schema also includes two @AppEnum types."}, {"start": 565.0, "text": "The schema defines the set of possible cases,"}, {"start": 568.0, "text": "and my app adopts the ones that apply."}, {"start": 570.0, "text": "These are also schematized, so I'll create them quickly using code snippets."}, {"start": 575.0, "text": "I'll use the calendar_attendeeStatus snippet for status."}, {"start": 580.0, "text": "The snippet comes with all the cases the schema supports."}, {"start": 583.0, "text": "CometCal's model already maps directly,"}, {"start": 585.0, "text": "so no changes are needed, but if an app uses different terminology,"}, {"start": 589.0, "text": "simply map the existing model to the schema's cases"}, {"start": 591.0, "text": "so Siri can recognize the shape."}, {"start": 594.0, "text": "Similarly, I'll use calendar_attendeeType for the attendee type."}, {"start": 599.0, "text": "The schema requires at least one case to describe what kind of attendee this is"}, {"start": 603.0, "text": "and since CometCal's attendees are all people, I'll add a person case."}, {"start": 611.0, "text": "I'll fill in the types for status and type respectively."}, {"start": 616.0, "text": "And that takes care of the AttendeeEntity."}, {"start": 622.0, "text": "That's two entities in orbit. One more to launch."}, {"start": 625.0, "text": "The calendar and attendee entities find their orbit with the next one…"}, {"start": 628.0, "text": "the gravitational center that pulls them all together..."}, {"start": 631.0, "text": "the EventEntity."}, {"start": 633.0, "text": "The system's search index really shines for the event entity."}, {"start": 636.0, "text": "When someone asks \"When is my crew lunch?\","}, {"start": 638.0, "text": "Siri can search the title."}, {"start": 640.0, "text": "When they ask \"What events mention oxygen?\","}, {"start": 642.0, "text": "it can search the note content."}, {"start": 645.0, "text": "People can ask questions about their data, and Siri answers directly."}, {"start": 649.0, "text": "Here's EventEntity with the calendar_event snippet already applied."}, {"start": 653.0, "text": "Just like CalendarEntity, the EventEntity conforms to the IndexedEntity protocol"}, {"start": 657.0, "text": "and includes the indexing in CalendarManager to take advantage of the semantic index."}, {"start": 662.0, "text": "There's a lot here."}, {"start": 664.0, "text": "The schema covers a wide range of properties."}, {"start": 666.0, "text": "But don't panic, the same patterns from the previous entities apply."}, {"start": 670.0, "text": "The main difference is the number and variety of parameters."}, {"start": 674.0, "text": "To keep this mission on schedule,"}, {"start": 676.0, "text": "the familiar pieces are wired up off-camera."}, {"start": 678.0, "text": "Feel free to pause and check the source code."}, {"start": 681.0, "text": "The schema defines which properties are required and which are optional."}, {"start": 685.0, "text": "The essentials like title or startDate are straightforward to wire up."}, {"start": 689.0, "text": "Optional properties that my app doesn't use,"}, {"start": 691.0, "text": "like travelTime or virtualLocation,"}, {"start": 694.0, "text": "can simply stay unset."}, {"start": 696.0, "text": "Properties that aren't part of the schema but exist on the data model,"}, {"start": 700.0, "text": "like isFavorite, can also be added to the entity."}, {"start": 703.0, "text": "What makes this entity interesting is"}, {"start": 705.0, "text": "how it composes with the other entities built earlier."}, {"start": 708.0, "text": "The calendar this event belongs to is a CalendarEntity…"}, {"start": 714.0, "text": "and the attendees is an array of AttendeeEntity."}, {"start": 721.0, "text": "Siri understands these relationships with App Schemas."}, {"start": 727.0, "text": "The recurrence property is also one worth quickly pointing out."}, {"start": 732.0, "text": "It can be used to represent events that repeat,"}, {"start": 734.0, "text": "like a weekly workout or an important yearly anniversary."}, {"start": 738.0, "text": "It uses Foundation's Calendar.RecurrenceRule type"}, {"start": 740.0, "text": "and converts to and from CometCal's simple frequency enum"}, {"start": 744.0, "text": "for cases like daily, weekly, monthly or yearly."}, {"start": 750.0, "text": "As part of the schema,"}, {"start": 751.0, "text": "there are also union values for an event's location and alarms."}, {"start": 756.0, "text": "A union value is a property that can hold one of several different types."}, {"start": 760.0, "text": "For example, the location can be either a PlaceDescriptor"}, {"start": 764.0, "text": "from the GeoToolbox framework, or a String."}, {"start": 767.0, "text": "Again, we can simply implement these via code snippets."}, {"start": 773.0, "text": "Like so."}, {"start": 774.0, "text": "For alarms, it can be either a Duration or a Date."}, {"start": 778.0, "text": "Assign the properties to these types accordingly."}, {"start": 786.0, "text": "Like the attendee, there are also schematized enums here"}, {"start": 789.0, "text": "like the EventEntityStatus."}, {"start": 791.0, "text": "Both enums related to events come complete from the snippets,"}, {"start": 794.0, "text": "so I'll add those and wire them up."}, {"start": 800.0, "text": "Lastly, assign the status property to the new status type."}, {"start": 804.0, "text": "And with that, the content layer is fully fueled and ready for launch."}, {"start": 809.0, "text": "Time to find out if this thing has liftoff!"}, {"start": 812.0, "text": "On the left is the detail view of the Meteor Shower Watch Party."}, {"start": 816.0, "text": "The time, location, and note are all there on screen…"}, {"start": 820.0, "text": "But imagine I'm mid-conversation with Siri talking about meteors,"}, {"start": 823.0, "text": "and I suddenly remember about the party."}, {"start": 826.0, "text": "Instead of leaving the conversation to open CometCal and find the details,"}, {"start": 830.0, "text": "I can just ask…"}, {"start": 831.0, "text": "\"Is the Meteor Shower Party happening anytime soon?\""}, {"start": 839.0, "text": "\"What's the weather like out there?\""}, {"start": 844.0, "text": "I can also switch to typing:"}, {"start": 847.0, "text": "\"When is the peak viewing time?\""}, {"start": 854.0, "text": "I can also tap on the result to take me to CometCal."}, {"start": 859.0, "text": "Siri answers every question using the app's content."}, {"start": 862.0, "text": "No need for custom natural language... just entities and schemas."}, {"start": 866.0, "text": "Now, you may have noticed"}, {"start": 867.0, "text": "that tapping the event from my conversation with Siri opens CometCal,"}, {"start": 871.0, "text": "but it just lands on the main screen."}, {"start": 873.0, "text": "It doesn't navigate to the event like I would expect."}, {"start": 876.0, "text": "Siri doesn't know how to open a specific event in the app yet."}, {"start": 880.0, "text": "I'm going to take a short detour to make this experience even better."}, {"start": 884.0, "text": "To get this working, here's an OpenEventIntent."}, {"start": 888.0, "text": "It's a small intent that conforms to the system.open schema."}, {"start": 891.0, "text": "It takes an EventEntity as its target"}, {"start": 894.0, "text": "and tells the NavigationManager to navigate to that event."}, {"start": 897.0, "text": "The system calls this whenever someone taps an event result in Spotlight or Siri,"}, {"start": 902.0, "text": "or asks Siri to open one."}, {"start": 908.0, "text": "And that's it!"}, {"start": 909.0, "text": "Now if I tap on an event…"}, {"start": 911.0, "text": "this time, CometCal opens straight to the detail view"}, {"start": 914.0, "text": "of my Meteor Shower Watch Party."}, {"start": 916.0, "text": "That's the OpenIntent bridging the gap."}, {"start": 919.0, "text": "Siri can now understand the app's content better than ever,"}, {"start": 923.0, "text": "so people can ask Siri about any of it."}, {"start": 925.0, "text": "Not bad for three structs and filling out a few code snippets, right?"}, {"start": 929.0, "text": "There's one more thing I can do to make my conversation with Siri"}, {"start": 932.0, "text": "feel even more natural."}, {"start": 933.0, "text": "When someone has a specific event on screen,"}, {"start": 936.0, "text": "they might want to say something like"}, {"start": 938.0, "text": "\"email the people in this event\" without having to say the event's title."}, {"start": 942.0, "text": "That's onscreen awareness... and it takes just two view modifiers."}, {"start": 946.0, "text": "In CometCal's CalendarListView, where it lists all of my events,"}, {"start": 950.0, "text": "I'll add an .appEntityIdentifier modifier to the list,"}, {"start": 953.0, "text": "passing in an EntityIdentifier for each of the event entities."}, {"start": 957.0, "text": "This connects the list to its entities, so when someone is browsing the list,"}, {"start": 961.0, "text": "the system knows which events are on screen."}, {"start": 964.0, "text": "In the event detail view, when a single event's details are on screen,"}, {"start": 968.0, "text": "I'll append a .userActivity modifier with an EntityIdentifier."}, {"start": 972.0, "text": "This tells the system that one specific event is front and center"}, {"start": 976.0, "text": "so Siri can resolve this event to exactly the one being viewed."}, {"start": 981.0, "text": "And that's it! Here's what it enables."}, {"start": 984.0, "text": "Now that Siri can understand what's currently onscreen and open event entities,"}, {"start": 988.0, "text": "I'll ask Siri to open the event in a natural way:"}, {"start": 991.0, "text": "\"Hey Siri, open that third event.\""}, {"start": 997.0, "text": "Now that I am currently on the Meteor Shower Watch Party detail view,"}, {"start": 1000.0, "text": "I'll try referring to this event rather than saying the entire title…"}, {"start": 1004.0, "text": "\"Hey Siri, email the people in this event"}, {"start": 1007.0, "text": "and ask someone to bring chocolate and marshmallows.\""}, {"start": 1012.0, "text": "Siri can use it's understanding of the onscreen event"}, {"start": 1014.0, "text": "to find the attendees and hand them off to Mail."}, {"start": 1018.0, "text": "Seriously… two modifiers…"}, {"start": 1020.0, "text": "that's all it takes to connect what's on screen to the app's content."}, {"start": 1025.0, "text": "Siri can now talk about the content."}, {"start": 1027.0, "text": "But to truly take off, I'll give Siri the ability to act."}, {"start": 1031.0, "text": "Once again, App Schemas lead the way,"}, {"start": 1033.0, "text": "and this is where things get really interesting."}, {"start": 1036.0, "text": "I'll start with creating events."}, {"start": 1038.0, "text": "Just like entities, intents use code snippets too."}, {"start": 1041.0, "text": "I'll find the calendar_createEvent snippet and select it."}, {"start": 1046.0, "text": "It scaffolds the intent with the @AppIntent macro,"}, {"start": 1049.0, "text": "the schema, all the parameters the schema requires, and a perform stub."}, {"start": 1054.0, "text": "The parameters, from title to note,"}, {"start": 1056.0, "text": "come from the schema and I can use them in the intent's perform logic."}, {"start": 1060.0, "text": "I'll start by filling in the types."}, {"start": 1063.0, "text": "Then I'll add a @Dependency"}, {"start": 1065.0, "text": "for the CalendarManager to use in the perform method."}, {"start": 1068.0, "text": "In the perform method,"}, {"start": 1069.0, "text": "I'll mark it @MainActor and set EventEntity as the return value type."}, {"start": 1075.0, "text": "The general pattern is straightforward:"}, {"start": 1077.0, "text": "resolve the intent's parameters into something the data layer understands,"}, {"start": 1081.0, "text": "perform the action, and return the result as an entity."}, {"start": 1085.0, "text": "For creating a calendar event,"}, {"start": 1087.0, "text": "that means resolving the parameters"}, {"start": 1088.0, "text": "like extracting the location from the union value,"}, {"start": 1092.0, "text": "and converting recurrence if provided."}, {"start": 1095.0, "text": "I'll pass everything to calendarManager's createEvent method"}, {"start": 1098.0, "text": "and return the result as an EventEntity."}, {"start": 1106.0, "text": "Here's what's remarkable."}, {"start": 1108.0, "text": "Because this conforms to an App Schema, Siri can handle all the heavy lifting."}, {"start": 1112.0, "text": "Interpreting language, asking for clarification, and confirming details…"}, {"start": 1117.0, "text": "so people can just have a natural conversation with Siri."}, {"start": 1120.0, "text": "Time to take it for a launch…"}, {"start": 1122.0, "text": "Imagine I want to create a new event,"}, {"start": 1124.0, "text": "but I'm mid-spacewalk and my iPhone is floating just out of reach."}, {"start": 1128.0, "text": "Instead of grabbing it, I'll just ask Siri..."}, {"start": 1131.0, "text": "\"Hey Siri, create a new event in the Lunar Orbit Log.\""}, {"start": 1140.0, "text": "\"Call it Zero Gravity Yoga for June 15th, 8am.\""}, {"start": 1145.0, "text": "Siri can resolve the title, the date, and the time of the new event."}, {"start": 1149.0, "text": "When it's done, the event is added to the calendar."}, {"start": 1154.0, "text": "Just like that, a few lines of code and the app works with Siri."}, {"start": 1158.0, "text": "That's the power of App Schemas."}, {"start": 1161.0, "text": "Now that events can be created with Siri,"}, {"start": 1163.0, "text": "I'll keep the momentum going with updating events."}, {"start": 1166.0, "text": "Here's UpdateEventIntent,"}, {"start": 1168.0, "text": "already filled out using the calendar_updateEvent snippet."}, {"start": 1172.0, "text": "The structure is similar to the create intent,"}, {"start": 1174.0, "text": "but the key difference is that most parameters are optional"}, {"start": 1177.0, "text": "since someone might only change one or two things."}, {"start": 1180.0, "text": "The event parameter is what Siri resolves; everything else is optional."}, {"start": 1184.0, "text": "The perform logic follows the same pattern: resolve each parameter if provided,"}, {"start": 1189.0, "text": "then pass everything to the CalendarManager's updateEvent method"}, {"start": 1192.0, "text": "and return the updated event."}, {"start": 1194.0, "text": "It might be more code than the create intent,"}, {"start": 1196.0, "text": "but there's nothing completely new happening here."}, {"start": 1200.0, "text": "However, there's one important subtlety"}, {"start": 1202.0, "text": "with optional parameters in update intents worth calling out."}, {"start": 1205.0, "text": "For example, when recurrence is nil,"}, {"start": 1207.0, "text": "does that mean \"don't change it\" or \"remove it\"?"}, {"start": 1211.0, "text": "A simple nil check doesn't tell me which case I'm dealing with."}, {"start": 1214.0, "text": "Zooming into the recurrence logic in the perform method,"}, {"start": 1217.0, "text": "the @AppIntent macro wraps each property in an IntentParameter"}, {"start": 1222.0, "text": "which exposes a valueState."}, {"start": 1223.0, "text": "This is how I tell the difference."}, {"start": 1226.0, "text": ".set with an actual value means a new value is provided."}, {"start": 1230.0, "text": ".set with a nil value means it's explicitly cleared."}, {"start": 1235.0, "text": ".unset means the parameter isn't part of the request."}, {"start": 1238.0, "text": "This pattern applies to any optional parameter"}, {"start": 1241.0, "text": "where clearing the value is a meaningful action."}, {"start": 1244.0, "text": "Now that the update intent is wired up,"}, {"start": 1246.0, "text": "I'll send a few commands into orbit:"}, {"start": 1248.0, "text": "\"Hey Siri, move this to 10 in the evening.\""}, {"start": 1255.0, "text": "\"Yep, that's fine\""}, {"start": 1260.0, "text": "\"Also, change this to repeat weekly and move it to my Deep Space calendar\""}, {"start": 1267.0, "text": "\"Sounds good\""}, {"start": 1271.0, "text": "\"Actually, do not repeat this event\""}, {"start": 1275.0, "text": "The detail view reflects every change."}, {"start": 1277.0, "text": "All from a few lines of code and an App Schema."}, {"start": 1281.0, "text": "That update works, but Siri displays a default result card."}, {"start": 1285.0, "text": "This is CometCal, it deserves something with more... atmosphere."}, {"start": 1290.0, "text": "By default, Siri builds the result card from the display representation."}, {"start": 1294.0, "text": "Snippet views let me replace that with a custom SwiftUI view."}, {"start": 1298.0, "text": "I've prepared a SwiftUI view that takes an EventEntity"}, {"start": 1301.0, "text": "and lays out the details in a stellar way."}, {"start": 1304.0, "text": "You can get really creative here…"}, {"start": 1305.0, "text": "but also remember to keep it simple and lightweight."}, {"start": 1309.0, "text": "To wire this all up, I'll open UpdateEventIntent."}, {"start": 1313.0, "text": "I'll add ShowsSnippetView to the perform method's return type."}, {"start": 1317.0, "text": "Then in the return statement, I'll pass the EventSnippetView."}, {"start": 1322.0, "text": "The same approach works for any other intent that returns a result,"}, {"start": 1325.0, "text": "like the create intent."}, {"start": 1328.0, "text": "\"Hey Siri, push the crew lunch out by an hour\""}, {"start": 1337.0, "text": "I now get the new snippet!"}, {"start": 1339.0, "text": "The cosmic gradient accent, dark blue background,"}, {"start": 1342.0, "text": "the updated event details and a star icon…"}, {"start": 1345.0, "text": "That's the app's personality shining through within Siri."}, {"start": 1349.0, "text": "That covers the update intent."}, {"start": 1350.0, "text": "The last action to wire up is delete, and it's the simplest of the three."}, {"start": 1355.0, "text": "Here's the DeleteEventIntent."}, {"start": 1357.0, "text": "This is pretty simple…"}, {"start": 1358.0, "text": "just the event and an optional span for recurring events."}, {"start": 1362.0, "text": "The perform logic finds the event and deletes it."}, {"start": 1365.0, "text": "Siri automatically handles the confirmation dialog before anything is removed."}, {"start": 1373.0, "text": "Time to test the ejection sequence:"}, {"start": 1376.0, "text": "\"Siri, delete that party.\""}, {"start": 1383.0, "text": "\"Yes, delete it.\""}, {"start": 1388.0, "text": "\"Also, delete the event happening June 9th.\""}, {"start": 1394.0, "text": "\"Oh… actually, never mind.\""}, {"start": 1399.0, "text": "Siri asks for confirmation before deleting any event,"}, {"start": 1403.0, "text": "and also makes sure to disambiguate when more than one event matches."}, {"start": 1407.0, "text": "Three intents."}, {"start": 1408.0, "text": "Full event management with Siri."}, {"start": 1409.0, "text": "Mission accomplished!"}, {"start": 1411.0, "text": "CometCal has gone from screen-bound to fully voice-piloted."}, {"start": 1415.0, "text": "A lot got built during this video."}, {"start": 1417.0, "text": "Here are some next steps for making your own apps work with Siri."}, {"start": 1421.0, "text": "Download the CometCal sample project and explore the full implementation."}, {"start": 1425.0, "text": "Browse the App Intents documentation"}, {"start": 1427.0, "text": "to explore all the available App Schemas and domains"}, {"start": 1431.0, "text": "For automated testing, check out this video to learn how to write tests for CometCal"}, {"start": 1435.0, "text": "using the new AppIntentsTesting framework."}, {"start": 1438.0, "text": "And watch \"Explore advanced App Intents features for Siri and Apple Intelligence\""}, {"start": 1442.0, "text": "to go deeper into ways to refine how your app works with Siri"}, {"start": 1445.0, "text": "that weren't covered in this video."}, {"start": 1447.0, "text": "You're now mission-ready to take your app to the final frontier."}, {"start": 1450.0, "text": "Thank you so much for following along!"}]}