Transcript for:
Building Your First App with SwiftUI and SwiftData

hello folks and welcome to this special live stream building your first app with swift UI and Swift data uh in this video we're going to build a complete app from scratch all focused on Swift data storing your data there safely you'll need some knowledge of swiftyy attach this already I'm not doing Swift from absolute Basics here but I will try and cover all the Swift data stuff from scratch so you can learn along the way if you're watching live uh this stream thing on the right here is the chat with window folks ask questions and get involved and so forth directly below the video by the way is a link to just giving because today is my birthday and I'm running this stream live to raise money for folks experiencing homelessness and so if you can I would very much appreciate if you could donate some money to the cause it's again folks who are experiencing homelessness or escaping domestic abuse or uh recovering from leaving prison and uh so much more they do wonderful wonderful work and your donation matters this winter now the app we're building folks I've got it here running on my iPhone already uh it's I I think it shows off quite a lot of Swift data already it's called face fact it's about helping you remember the folks you have met at various events meetups whatever uh at your workplace school who knows what and you can see here you can add people to the listing here you can swipe the deletes you can sort them in different ways you can filter them with searching you can go into them and attach pictures are relationships you can track where they were met and so forth there's a lot of stuff in this tutorial really really cramming it in I've tried to get as much of uh Swift data as I can into a single uh stream ah great thanks for donation folks honestly every pound every dollar every Euro everything you can have helps at this time of year because it is quite viciously Cold anyway along the way you're going to learn lots about Swift data about Swift why there's so much going on if you haven't seen Swift data before it is Apple's framework for storing data querying data persisting data generally uh in an effective efficient way it's lots of fun to work with and as you'll see you can get a lot done very quickly you will need to have installed xcode 15 or later you can see I'm using xco 15.1 here 15.2 is fine uh you will Al have to have have uh iOS 17 obviously attached to it I'm running 17.2 here in the simulator um a real device is obviously preferable but you know simulator works very well too uh and along the way if you are coming on live please ask questions I'm here to answer questions and help you understand things better so if you are here live ask questions get involved it's a great way to learn to uh question already how long do I think the stream will take I've got no idea I mean let's call it two hours but it depends on the questions and tangents and diversions along the way there's lot to see we'll see anyway to get started we're going to go ahead and make a oh dogs already we're going to go ahead and make a new project in xcode here so choose xcode uh create a new project then choose iOS and app then press next like that hello with dog boom there you go uh now the app we're making is called face facts because it's facts about faces you see I'm being clever here uh thank so much Oscar Alvarez is very generous of you uh and Simon as well thank you so much anyway uh it's called face facts facts about faces you know it's a nice sort of punish kind of name here um for Swifty interface that makes sense but please don't change storage to Swift data leave it as none we'll bring the whole thing in from scratch ourselves right have X code make a ton of code for us here so press next and then create that somewhere you can get to I'm going to go ahead while that's working and reset my simulator to destroy the current copy of the app I have so we start from a clean slate in my simulator so I'll just do erase all content settings like that boom uh my dog this one's called Arya the very hungry one the one down here Out Of Reach is Luna but she knows when his treats going she can get some one more each and you go away okay I got work to do one for you one for you all right out out out out SC ouch dear me okay yeah the dogs always know what's going uh Stephen no there's no plan to do widgets we'll do cloudkit though so you know you win some you lose some so uh I have reset this here's our basic uh xcode app template Swifty y here again no Swift data it's just pure uh Swifty white stuff which is great the first thing we going to do is bring Swift data into our project and there are three fairly small steps to do this first you want to Def find a data you want to work with like what you actually want to store second make some storage for that data and third go ahead and read whatever you want wherever you wanted and they're all honestly fairly small steps Tyler know there'll be no enums involved in this application so first we going do is design our data like what do we actually want to store now for us this will be simple add a little bit more later on we'll start simple we'll store three pieces of information about a given user we'll store their name the email address and then a free text details uh field you just add any extra information you want about that user so the first step here is to press command n and make a new Swift file under iOS here then call this thing person. Swift this will will Define a single person in our application what do we know about a single person uh in our case like I said we're going to store those three things the name email address and details so I'll make inside here a new class called person that stores those three piece of information so we say here we want the name as a string the email address as a string and the details as a string so three simple strings in one class so each person has those three things there now because it's a class we got to add izer for it it's required for classes and so we'll add one here just go into the class type I in it'll just spill it in for you enter boom initializer done now this is a boring Swift class nothing special at this there's no Swift data here at all it's just a regular Swift class what we want to do is tell s data that this thing can be loaded and saved and to do that we're going to add and import for Swift data at the top and then add a macro here to say this is handled by Swift data we say at model class person so both Glenn and Aaron asking why use a struct around on the class so if you imagine a struct of data and you have it say in the home screen of a user and you go to detail screen and then go somewhere else and somewhere else and somewhere else and somewhere in the app you change that value you edit the user's name or email address or details how would you make sure that same value also changes everywhere else that's you used in your app now with classes they're automatically shared change one they all change with strs that wouldn't happen so what they could do is say well you know we want to use strs we like strs very much in Swift what if we had a special little pointer ID tag in our struct that refers to it uniquely so when it changes everywhere else that same ID looks at that ID and says okay I'll change as well and they refresh themselves they they could have done that but what they've basically made is classes they basically made a pointed to a class which is how classes work in the first place it wouldn't be any more effective and so uh in Swift data nearly all the time you want to use classes and not structs particularly when dealing with relations as we get later on they're very messy with struct I found I wouldn't recommend them simple value structs work okay but they make it rather hard to query so yes you do want classes here so macros like this one here rewrite our code at compile time adding extra functionality in this case it rewrites this person class to add all the functionality to back this thing by Swift data these are not just simple strings anymore they instead read and write string values from Swift data's long-term storage now if you ever want to see what a macro does just right click on it and choose expand macro and you'll see that's what it actually rewrites our class into lots of things happening here and you can see that name and email address and details are all now marked as posed properties the underscore means it's kind of private don't use me directly that's also macro which you can expand further and see there's more code inside that there are now gets and Setters to read and write the values from Swift data so that one app model macro does lots of work for us uh you can go ahead and click hide macro expansion to get rid of all the junk so it looks like we're handing a simple class well not really lots of work happening here behind the scenes that's the first step we've now defined our data we want to store a person each person has a name name email address and detail string attached to them the second step is the tail Swift data we want to use this person class in our application and this is done by making a model container for the class which is Swift data's way of loading and saving values from the iPhone's SSD so over in our face fact app struct here we're going to add one modifier to the window group called Model container 4 we'll write here model container for person. self that thing comes from Swift data add another import here Swift data and that's step two done now what this thing does is when that app runs the very very first time Swift data will say okay you want to use people that's fine it's person class here I'm going to go ahead and make the underlying database storage to hold a person object will make over time but then all subsequent runs 2 third fourth fifth thousandth whatever it loads that existing database for us to use so it creates it once and loads it all subsequent times now behind the scenes Swift data is read as core data which itself just backs on to SQL light a common database format uh system sorry not really format uh of course s then adds on top of that things like iCloud synchronization and much much more it's very powerful but it is ultimately a database behind the scenes being made for us so that's step one and step two now complete the third step is to go ahead and read some data wherever you want it now for us that'll be in content view at least for now and so in content view up here I'm going to go ahead and make a query I'll say hey data give me all the person objects you're managing no good Nick make sure you have that import Swift data add it as well Ryan at Main is not a macro at Main is special and separate so uh here in our main content view here I'm going to say just go ahead and query for me all the person objects we have here and so we can say again Imports of data and then at query oops at query even V people is an array of person that's it that tells Swift data please go ahead and load all the person objects you're managing right now and that's literally all it takes even better this at query which another macro by the way will automatically keep that array up to date so value changes we add a value delete a person whatever that will refresh Swifty y view automatically for us keeping up to date at all times so it'll load all the people and stay up to dat over time it's absolutely brilliant we'll look at things like sorting filtering and more later on but for now we're basically done with our Swift data setup code we can add just enough sfty y to kind of show things on the screen so I'll say this default body code can just go away I'll ask for a navigation stack with a list inside and in the list will be a four each over that people array give me one person coming in then we'll make a navigation link for that person with the value of the person itself let's shrink a sidebar down slightly there we go boom and then do a text of the person's name inside We'll add a little title like nav title is called face facts we add a little navigation destination uh when we're given a person go ahead and give me that person coming in and just show it in the text to you for now we'll do more later on but it's enough for now like that so that is our basic Swift UI code just to work with our Swift data objects you can if you want to run the app I wouldn't there's no point it'd be rather dull because yes all our Swift data codes in place and our UI work correctly but there's no actual people being stored yet we haven't made any data let's fix that next speaking of which I should remind you I have a chance uh somewhere just giving I have a link I should give you really Al I put into the thing perhaps this one that's the one yeah that's my just giving link I put into the YouTube chat here if you want to donate to this course you can do I would much appreciate this is all going towards Julian house which help folks who are experiencing homelessness escaping domestic abuse and so much more and we already almost doubled our Target thank you so much okay other folks post a link on that future in the future perhaps Okay so we've got our our setup data all in place it's now time to actually handle aditing and editing users somehow and of course we want to just say yes go ahead and add custom data but then let them change their mind edit it and delete it whatever freely over time if you look at the way Apple's Notes app solve adding and editing it's brilliantly simple when you press the plus sign to add a new note it creates an empty note and then navigates to it straight away what this means is adding and editing are really the same thing they not two different views entirely it eliminates lots of extra work we can take exactly the same approach here we can make a new method that creates a new blank person object then immediately navigate that to editing so just create and push to it straight away so editing the new thing straight away completing this thing takes I think four steps in total first we'll make a new view that handles editing person data again our person class is very simple right now we'll add more later on to it second we want to change our current navigation stack so we can control its path programmatically want to push to the the new person when they're ready third uh we'll make a Content view uh method here makes the person and navigates to it and fourth we'll call out from a toolbar item uh David asks uh if person changes M presses back we delete the note yeah if you want to sure it's down to you I mean how much data they have to provide till you're happy it's empty for example if they provide just details text should you delete the user or if it's all empty do you delete the user I don't know I'm not going to do it here but notes just says okay the note was empty just delete the note entirely it's down to you Porter asks does Swift data automatically handle DB migrations it does its best you'll see one here later on uh Tyler asks if you want to present a person's name only no list uh so it it's designed to load arays queries doesn't handle single objects at this time file Fe feedback app if you want to asking for that you could of course just read the first object from the array which happens to contain one object that would work too but right now it loads arrays once we have those four tasks done we can go ahead and add edit users freely of an actual usable app and so let's get started now let's press command n make another new file choose Swifty view this time and call this thing edit person view like that now this thing needs to know really only one piece of data which is the person we're editing so I'll press uh create in there and give it exactly one property our person is a person that's what we're editing right now now that's immediately as you can see going to cause an error down in our views preview area because it wants to know what person to pass in for previewing purposes rather than correct this error now for now just for now select it all and press command slash to comment it out the com prev out entirely we will fix this later on in one big lump but all I can get the main app finished first otherwise doing a lot of previewing jumping around let's try and stick to one task at a time building the app right now thank you for donations folks it all helps so we have now a new view with a personal property inside and the preview is conted out our current person class has three properties name email address and details and want to edit all three of them as text Fields here but there is one small speed bumper if we start filling in the body of this view you'll see it pretty quickly I'll make a form with one section inside and inside there will be a text field prompting for their name and binding the text to Dollar person. name I'll give this thing a text content type of name and then add a nav title of edit person and a navigation bar title display mode of do inline that's our starting form and boom there's a speed bump Swift does not understand what dollar person means in this Scope when we have a local property declared using at state or similar Swift UI automatically makes for us three ways of accessing that property for example we had an integer called age and we read age directly just AG G directly we can get or set integer there's no wrap around it just indirectly okay if we used dollar sign age then we get a binding to the data which is a two-way connection to the data that we can attach to Swift UI views for example for integers we might say there's a stepper controlling someone's age and you can press up and down and it'll change the property as stepper changes change stepper property updates change property stepper updates it's a two-way connection we can also write underscore age the Third Way which lets us access the underlying State Property rapper directly it's a struct behind the scenes which is helpful when you want to initialize in a very custom way so three ways of getting our data that's with at state but here we aren't using at State this this is just a simple basic instance of our person class it has no way of exposing bindings to us with text field or any other Swift UI views now fortunately this is easy to fix because Swift UI has a property rapper automatically makes for us bindings to an object in fact all it takes is adding that one property rapper here and the problem just goes away we can write at bind like that so our view still expects to be handed a person object to edit but when it gets passed in Swift UI will autoally automatically make bindings from that for us with the app bindable wrapper so meaning we can now use dollar personname to get bindings to the underlying string just like we wanted in the first place some questions uh Sia says I don't really like 50 y Feels Like Heaven oh like UI kit few uh I think folks on 50 y honestly at this point UI kit is kind of uh fading in its career I do WR a lot of code in youu like kit but 50i is very very much on the rise and apple are not beating around the bush in that front uh pa5 AA um should you make view models if you want to uh you find it very very hard and Swift data it's an absolute nightmare Swift data mbbm and so I wouldn't necessar recommend anyway with that bindable wrapper in place we can now go ahead and fill in the rest of our form I can say down here a text field for the email address with its text bound to Dollar person. email address person. email address go on email address ah I give up it's welome to live streams folks um with a text content type of email address and a text input autoc capitalization of never MOX ific you want at bindable here this is used with an observable object type that's an observable uh macro object type to create bindings from it at binding is used for uh local copies or local references sorry of external State different thing very similar naming though which is not helpful but you do want that bindable here Ryan no at bindable does not replace that uh observed object is replaced by observable the macro yes how to fold make sure you have made your person class use the at model macro if you have not done that it will not work okay so uh we'll also add another section below for our notes so section here notes uh text field we'll do details about this person with a text bound to Dollar person. details and I'll add this time axis. vertical meaning that this text field will grow vertically as user types more than one line it's great for long sort of form detail text stuff we'll add more later on but it's enough for now that's a first step done A View to edit our person data the second step is to upgrade our navigation stack so we can control its path programmatically we can add things to there and therefore push to a new view when we're ready this means making some local state in our content view to store the current path and then bind that path to the navigation stack now navigation Parts can contain all sorts of objects for now we just need one type of data the try to person we're trying to edit in the first place and so our path will simply be an array of people uh Peter this text content type modifier tells iOS to expect a name being typed in here and so that will'll get autoc completion options for the name or for the email address it's a nice user Helper and it's actually find it very annoying when apps don't use this because you got type in your full email address by hand just you know G anyway uh back in our content view we're going to make some state to store uh path we're working with so I'll say up here at State private VAR path is an array of person person like that uh Justin wouldn't I use so for this one I honestly wouldn't recommend using senten someone's name it's complicated uh and I think when you try and make assumptions about names it nearly always goes wrong um I'm think in particular like Dutch names like my friend antoan fond uh fond de L the V and the D is that Caps or not I don't know it's best not assuming just stick with the name and let iOS sort it out anyway this is our path we want to to have in our navigation stack here and now we can bind that to our navigation stack we'll just say this thing has a path of dollar path now this is a two-way binding for the nav stack what that means is as the user manually goes between views it will change this array to match what they're viewing but two way if we change the array it will push or pop things from navigation stack the third step here in this little goal is to write a method in our content view that creates a person then navigates to it immediately this step is slightly more complex uh and so we'll break this thing down to like four mini steps okay first we're going to get access to where Swift data stores information second want to make our data third we want to tell Swift data hey this matters please store it and fourth want to navigate to the editing screen so four little tiny little mini steps here first of these requires introducing another important Swift data concept called a model context now you already met model containers over here we make one right in our face fact abstract okay this thing is responsible for loading and saving from the iPhone's permanent storage actually making the database for Us loading it preparing it handling syncing or whatever it's all done through model container OKAY model contexts are are a bit like a data cache if you imagine reading and writing Swift data objects from dis again and again and again for every little request you can see pretty quickly it' be inefficient okay so so what happens is Swift data gives us a model context it loads objects into RAM into the context works with them there and then writes them out when we're finished and so when we used at query over here in content view swifta no okay I load these people from SSD into our model context into RAM and it stores them there keeps them alive there temporarily we can then go ahead and make all the changes you want to adding things deleting things you can modify things much you want to and at some point in the future they'll be written back out to dis so it's it goes from Ram back to permanent storage once the save is triggered uh does model container work in multi mul IM module projects goodness me well you can have as many model containers as you want to if you genuinely want to have that um they are the only thing in Swift data that are safe in concurrency to passed around freely don't do it with context contexts are dangerous to be shared don't do that but uh containers are safe to be passed around between different parts of your app different actas for example uh and so you can do that and so yes if you want to have containers in different ways just be careful I think sometimes folks think that um I'll have this later in this container this one this container this one over here but if they are somehow linked together if this container loads some data from over here it'll push across the other container so it can kind of pollute your data very easily so be be careful with that one anyway the query that thing says get me all people from disk here and put in Ram basically put in my context to read and write these things here as much as they want to so this model context allows Swift dat to batch things together efficiently but it also means when we make a new person object and we're adding a personal database we don't just say please write the permanent storage now instead we insert it into the model context and let Swift data take over from there it'll be saved automatically in the future so this is where Swift data does three very cool things on our behalf first when we used this model container for person. self thing it silently made a model context for us called the main context this one always runs on Swift's main actor so it's safe to use from all our Swift UI code second it automatically place that model context into Swift ui's environment so we can read it out and use it to set our own objects in the future and third this query macro knows to look in the Swifty wi environment to find that context that's why it's able to load all our data without any extra work it talks to the environment finds the context finds container loads it all for us automatically it's very very nice so our first mini step here is to get access to where Swift data stores all its information which means we're going to read that shared model context that main context out of Swift's environment so we'll say here at environment back slash. model context V model context Glenn does the OS manage the size of the model context yes it does Matas wants me to do some exact coded project that feels like some sort of college homework project mat do it yourself anyway um I know that was a lot of explaining for like one line of code okay but it's hopefully understand what these model contexts do and why they're important the second Min step is actually making our data and to add a new property uh new method down here called add person this will make the person object so I'll say our person is a new person named uh nothing email address nothing details nothing I'm giving them empty text for all three properties which means when you display for editing usually to see placeholder prompts of all those fields rather just some sort of dummy text our third Min step is telling Swift data hey please store this new person for us just making it is not enough just saying there's a new person is not enough you want actually store it away somewhere this means inserting it into our model context which takes just one line of code after this I'm going to say model context. insert that person person person like that that is three out of four many steps done the final mini step we've now made our person we've added to Swift dat's context here we're going to navigate to our editing screen this means changing that Path property we made earlier this one here to say this this is the person we are now editing please push onto the screen and so here after the insert call I'm going to do path. append person so please navigate there now right now our navigation destination is just saying show the person's name is text clearly that's not enough anymore we want to navigate to our edit person view instead so I replace that text with edit person view person of whatever person they chose like that so that is in the in the bigger goal here that's now three out of the four big steps done towards a larger goal the last one is simply calling at person from a toolbar button when we're ready and so after the nav destination I'll say there is a toolbar with a button saying add person system image can be plus and the action will be add person like that and now go ahead and run the app I think it actually should work fairly well let's find out it's thinking at full speed so need to show some AI magic so need to you may or may not know that chat GPT has a current knowledge cut off of April 2023 three in its head I don't think Swift data even exists uh so good luck with that anyway face fact here I'm going to press plus and add a person here my name is going to be uh come on hi the keyboard please Dave lisst email address Dave red dwarf. comom and then go back boom so we've pressed plus it brings the editing screen straight away I fill in I've gone back see the person right there I can go in and say oh not Dave he prefers I don't know David and then go back that updates nicely as well I can then say okay let's leave the app entirely and then quit the app relaunch the app he's still there with changes in place given how little Swift data code written I think actually works pretty well it's doing a heck of a lot of work for us here not only is it correctly reading and writing data for us but also keeping all Swifty's views in sync as it progresses so that is adding dat completed it's a good reminder folks I am doing this live stream on my birthday because I'm raising money for folks experiencing homelessness go to the just giving link below the video if you want to donate the name will appear on the screen as well with how much you donate no pressure all donations are most welcome here it is winter it's cold outside folks need our help okay now we can add stuff let's do the next thing here because there are four basic tasks folks want to do when they have a database driven tool like this one here they want to create stuff they want to read stuff want to update stuff they want to delete stuff often just called crud create read update delete right now we have the first three of those we've got C or something right to add the extra D to this to get deleting working we're going to add a method in this uh view here that can delete delete people from our model context based on whatever data Swift UI passes in so we're going to attach that to an onite modifier and it will work correctly uh betto's getting some warnings ignore them that's Swifty why just spouting junk uh Roman asks uh I actually use the SQL light3 command line tool normally Roman it works very very well uh Andrea uh is confused at navigation links versus nav destination nav links passes data in please go to this please navigation destination says when you receive one of these values here's how you handle it so it's data being passed in data being received and acted on so it's separate I mean you can you can uh try good luck uh it becomes if I remember correctly query is a macro which backs onto a query property rapper it's kind of confusing and a bit uh difficult so I wouldn't recommend it uh Serius cat suggests uh maybe having a save button that you could add that of course you can add that if you wanted to um broadly speaking we use instant apply on Mac OS and iOS in fact all Apple's platforms prefer to just save it and sash it away and provide undo later on if you want to um as opposed to making them press save and taking extra actions prer things just change if possible so I wouldn't do that anyway deleting stuff in the same way as inserting is just calling model convex do insert deleting just calling modelc con. delete telling it what to delete if you ever used uh Swifty wise on delete modifier before you'll know it expects to call a method that accepts an index set a whole bunch of positions of items to remove from an array we just Loop over that and say delete these ones here here here and here and then it'll take care of the rest so in our content view I'm going to say there's a method down here called delete people uh at offsets index set then Loop over all the offsets offset in offsets and then do let person is people at the offset and call model context. delete the person that's it go over all the indexes ask to remove find the person at that index delete them and with that in place we can now attach an on delete modifier to our for reach up here somewhere this one uh we can do on delete perform on delete perform delete people that's that and that is deleting done so we now have creating reading updating and deleting the full crud stack in not a great many lines of code quite frankly Tyler asks how do you delete all if you had multiple if you want to delete every person you can do that in your model context you will see there is a delete Model option here you can say delete person. self like that that would delete uh all people ahead of time that would obviously blank out all the data so use that one carefully uh John Matt please don't use web views they weren't cool in 2008 they're still not cool today they're in fact terrible okay so now we have deleting done at this step you're probably thinking well Swift looks pretty easy you know we can just run some code it kind of taste care of lots of things on our behalf hooray and connects really neatly to sft Y it's very very easy well our next task is more challenging we're going to let users filter our list of people based on a search string this is tricky because uh in Swift data we cannot dynamically change the sort order for a query you just can't do it uh instead we're going to go and push it somewhere else hi Michaela um that alone wouldn't be too hard okay except for the fact that we literally cannot change the query in place we can't even make a new query here in content view we can't change it here because uh the the yes the data might change over time but the query itself the object itself is read only why no action with path in the delete function um because swipe delete happens only on the main list when the path will be empty like that so this is going to be tricky this is the tricky part on this whole project here we're going to rearrange our code just a little bit okay we're going to make our content view responsible for handling navigation stack that means it's title it means nav destination it means toolbar and so forth but we'll make a sub view a view inside content view responsible for handling that Swift data query this means content view can also handle searching and later on sorting as well and then we change the search text it will recreate the subview and in doing so recreate the subviews Swift data query so let's do this step by step first things first let some new state here in content view to store whatever text the user is currently searching for I'm going to say at State private VAR search text is an empty string by default second we're going to bind that to a Swifty y search bar using the searchable modifier this is simple enough we just go down here to our list and say under the toolbar searchable using the text of dollar search text that will show a search bar handle search text coming in and out and synchronize it into the property Force automatically now for the tricky part we want to try and sprit lit our content view into two parts so the query the list and delete people that method all go into a sub view that can be recreated whenever our search text changes so press command then make a new sa y view called people view folks I'm going to says one last time yes the video will be available after the stream finishes and yes the code will be on GitHub if you are watching along and soone in the future when so in the future ask that same question please reply for me be very very helpful thank you so much New View people view here step one we're going to move from content view this people property the whole act query V people person array grab that command X paste it into people view down here that you have to add a swift data import so query works correctly like that next I going to copy the model context from content view to people view don't move it we want in both places because uh content view calls add person people view will call delete both require the model context so keep M context in both places so over in content view here I'm going to say this context select it all and just paste it into there in both places step three want to move the whole delete people method from content view into people view uh so over here delete people grab that thing command X from content View and then command V into people view next up want to move the whole list code so this whole list here not the modifiers just the list itself grab that command X that move it into the body of people View and now All That Remains is to change our content view so we use a people view here like that boom now you can if you want to run the app again but there is no point it's identical we've just moved our code around slightly code that was previously right here in content view has been pushed one level deeper into people view however this reorganization serves an important purpose we can now make a custom initializer for people view that accepts a string to search for and use that to remake its query every time that search string changes now filtering in Swift data requires some very precise code so for now we're going to use just placeholder code just to get it started I'm going to fill in the rest of the code shortly but we'll just get it at least up and running first so in people view uh oh stream's buffering oh no make sure you hit a problem folks hit command R to refresh your uh YouTube stream that might clear up we'll see I I'm fairly sure it's okay for me but obviously I'm the one streaming so who knows uh anyway we're going to add the custom initializer here so I'll say in it here we expect to be given that search string which will be a string equal to an empty string by default like that so I expect to be told what to search for and then inside that again we're going to come back to this in a minute to explain it more detail but for now we're just going to saycore people is a new query with a filter being hash predicate person coming in true like that so we're giving this search string here a default value of an empty string which means we haven't got to change a preview code that'll still work fine however we do want to to pass in the search string from content view so as user types into the search bar it automatically changes people view it recreates it every single time and doing so remakes the query every single time and so in content view we'll say this people view here we're going to pass it the search string of our local search text State like that the code builds doesn't do very much but it builds let's now look at what it's actually doing behind the scenes is it efficient yes it is David this small piece of code does let's hide this bar perhaps does three things all at once and behind the scenes it leverages some of the most advanced Swift Code I've ever seen it's absolutely remarkable here filtering a swift data query is done by applying a series of predicates these are simple tests we can apply to individual objects in our data individual people what's happening is Swift data is going to say I'm going to hand you one person just a single person from my data and your job is to return true if that person should be in the final array or false if they shouldn't be in the array Mark epic the underscore is required now remember behind the scenes under the hood Swift data stores all this information in a database called SQL light this database has no idea about Swift Code doesn't care a jot about Swift and so Swift does something remarkable quite quite magical it's able to convert Swift code into structured query language or SQL which is a language used to talk to to databases like SQL light now it can convert all Swift Code obviously and in fact only supports a fairly small subset of Swift however once you get the hang of it you'll find these predicates work really well so with that in mind let's look at our code so far this thing here this hash predicate part here this is another macro it's enabled to rewrite our code like I said this behind the scenes con convert our code hi B our code to SQL to run on the database here it doesn't do it directly though if you right click on predicate here and choose hopefully uh expand macro there we go expand macro boom you'll see what it's doing it converts our Swift Code this one true into a foundation predicate with a predicate expression inside containing the word true so it's a series of of predicates and expressions it gets converted to and then when the code actually runs these predicates and expressions they converted into SQL that actually runs at runtime the best part is this whole process is completely transparent to us for the most part you haven't got to care about it that's the first thing it's a macro that converts our Swift code to SQL at runtime the second part here is is we're receiving one person to check here's an object should it be in the array or not and right now we're just saying true that means our filter does nothing we're saying yes this person matches my test include them in the array so every user matches the test now obviously we want something more interesting here we want to say actually only show people who have a name matching the search string now now we could write that by saying does the person have a name property that contains our search string but that's not ideal it's not ideal because contains is case sensitive so you think okay well okay it doesn't handle case very well I'm just going to lowercase the name and lowercase the search string so you might do person. name . lowercase do contain search string. lowercase problem solved great error sorry lowcase isn't supported in predicate again like I said only a limited subset of Swift Works inside macros let predicate macro here this is a good example we can't just use lowercase here it's just not supported helpfully Swift provides a a better solution here rather than saying contains we can say perform a localized standard contains which the same as contains except it ignores Case by defaults and it also ignores diacritics which means it ignores things like um acute accents and grav accents and macrons and similar and so a better way is to write our predicate like this does a person's name localized standard contains that that search string that's a big Improvement and it almost works almost works there's a problem by default our search string is empty and right now we're saying we want to send everyone back uh who has an empty string in their name which doesn't make sense right so the final version this predicate is actually going to say is the search ring empty if it is this person matches if it isn't but form a localized standard contains so we'll say if the search string is empty then true otherwise the same code before and with that in place that is our query working quite nicely hello dogs the query is not working nicely now the third thing this code does is as some asked earlier we're using underscore people here not just people this let us act as the underlying query itself now if we just use people by itself you'll see in my co- completion this thing is an array of person you can't put a query into it that's how property rappers work in the first place they kind of masquerade their contents as being the actual data array of person or integer whatever happens to be right by using underscore we're able to access the inside thing if I write underscore people here you'll see oh now we're changing the query that makes people so by using underscore people rather than just people we're saying change the query itself not just changing the array that results from the query and now hopefully I can press command R and have searching working well let's add another user here I'm going to add let's do Arnold rimmer email address Ace Rd dwarf. comom uh notes a total git and then go back uh I can bring up the search by sliding down a little bit and then I'll type in here let's do d uh a V great and you saw I typed D Rimer still matches because of the lowercase D it is case insensitive if I just do mm only rer matches okay questions questions Galore cranky we've got here can you change s to change from Oracle db2 no you cannot Michael um in core data there were a variety of older backends you could write to for example you could write to XML if you wanted to that is not the case in Swift data that's all gone away now it's always SQL light um how is it compared to fetching and filtering the array it's much faster David so the data is all there on disk it only loads the ones that match into RAM to be returned back to you as opposed to loading them all into Swift then filtering out from there if you imagine it loads 100,000 objects into RAM and then Swift throws most away it'll be quite inefficient this way I can kind of stream them in more effectively which is better anyway this is working well you can filter users but we can do better because if user types Parts On's email address for example that should also be included in the filter ring so really we want to say is uh if the name matches or if the email address matches or if the details match the search then return true and Swift that means using the binary or operator which is pipe pipe and so here I'll just copy and paste this a few times that pipe pipe paste pipe pipe paste and I'll change uh this one to be person email address and person details like so so it's now going to match all three things here it is a small but welcome Improvement now tip for you in Swift data these predicates are checked in order so it does a first one then second one then third one so it's generally best to arrange these in an efficient order that might mean putting faster checks first like comparing integers for comparing strings it might mean putting checks at the front that more likely to eliminate users you know the name seems the most common one to me so I put that one first here it depends on the app You're Building uh David says should we debounce for searching no need it is lightning fast I really wouldn't worry you know fun fact for you uh core data has an equivalent model context an NS managed object context and for years for years even today we've all been told before you call save on your managed OB object context always always check has changes first don't call Save if there are no changes you're just wasting time and at dubdub this year I'm like is that the case with swift data it seems to say things automatically all the time is is that still okay I said yeah don't worry about it it's perfectly fine we've got no idea why the docs say that cool so all this time been saying you know always always check check these things it really really matters and they're like no don't worry about it it's fine we check that internally anyway cool Antonio asks is there a limit on how heavy the database can be you mean how just big it can be no crack on I wouldn't store binary blobs in there images movies and similar I would showing you how that's done later on um but you know make it as big as you like quite frankly Thomas why is details green ah XC code I don't know it does that quite a lot it thinks some things are one thing something a different thing I don't know it probably recognizes from somewhere else what do you think it is oh it's not the right thing it's just being silly xcode I don't know so now we have filtering buy free text I'm going to pause for a minute to say an important message below the video stream it's a just giving link where you can donate to support this stream I am raising money for folks experiencing homelessness this winter escaping domestic abuse this winter your support matters it is cold out s they need our help if you can please donate thank you very much now we have searching the next step is going to be sorting this actually a lot easier now because it builds on the same principle we have for filtering here because just like we can't change a predicate dynamically we also can't change sort order dynamically and that means we also have to inject a sort order into the initializer here to change it automatically now sorting in Swift data can be done in two different ways the one we're going to use here is both easy and Powerful we're going to pass in an array of a new type called sort descriptor this lists properties to sort on as well as if it should be a sending Like A to Z or reverse sending descending which is Z to A and so over in our content view I'm going to add a new property to store our default sort order I'm going to say at State private VAR sort order is an array of and I'll say there's one sort descriptor using person. name with a back slash in front of it like that it's an array containing a single sort descriptor with a key path point to a person's name here we're saying sort our array sort our results automatically by the people's names I've marked it with at State this is going to change over time exactly what we'll do next here to change of sort order we're going to make a picker that will be bound to that sort order property here and inside there we can add various text options containing all the sort options you want to have maybe it's by name name reverse email address I don't know it's down to you your sorting but here's the important part no matter how you do it each option you have must contain a tag that changes this sort order to something else that's meaningful a different different array of sorting options I'm going to add just two options here name A to Z and name Zed to a so alphabetical and reverse alphabetical here and so in our toolbar I'm going to make a menu called sort with the system image of so I get right first time of arrow. up. arrow. down yeah and then inside there is a pi saying sort with its selection bound to Dollar sort order like I said there are two options we'll do text name A to Z and text name z2a like I said earlier we got to give these things tags so it knows which ones which knows what to do with a value when it's selected what to assign a sord order now for name a to zed it's just this array right here the same thing we have right now so I'll say this name has a tag of the default sort descrip option that's fine but for name Z to A I'll do tag the same thing and then change it slightly it's sorting by a person's name but I'm going to add order do reverse go in reverse alphabetical order here so the Picker and a menu this is helpful this really matters because by default without a menu the pickle will show its full option in the toolbar it looks a bit silly let's try it now see what I mean I'll press command R won't work yet you'll see it there's our menu and pressing it activates a picker I can choose name Z to A for example like that if I had not use the menu if I just comment out temporarily had the Picker directly inside the toolbar you get a very annoying layout you get name A to Z showing and then Z to a uh which just looks weird it's not like obvious what that even means and so wrapping that in a menu really helps so now we have all the UI to control our sorting we don't actually do any sorting yet for that we're going to change our people view initializer this one here so accepts a sort descript or array to apply to its query and so we'll modify this we'll say you now have the search string fine and a sort order which is an array of sort descriptor that describes the person object now this thing sort descriptor as you can see is generic uses Swift's generics system this is not any sort descriptor it's sort script describing a person object it sorts on name or email address or all details and nothing else to actually apply that to our query we're going to pass a second value down here into the query just after the predicate so right here we're going to say the sort should be sort order just pass that right on now doing that is going to cause a problem up here any default value that's fine boom like that that works well but but it'll break our content view because we aren't passing in a sort order so we want to actually use that thing somehow and so we're going to pass in our sort order alongside the search string in our content view up where is it here boom We to say sort order is our sort order property and that is sorting done I press command R run the code back uh so right now it's sorting a to zed so Arnold before David let's do Z to A David for Arnold it's working nicely why is it a list and not a value I literally don't know that means Maric sorry uh is there some more context than that one perhaps before I try answer it while they are retyping their question just below the video folks is link to just giving you can help contribute money to support folks experiencing homelessness today's my birthday fol if you want to give me a birthday gift please donate on just giving support folks experiencing homelessness so at this point we're like an hour and 10 minutes in we've got a basic uh app running here but now I want to take it a step further I want to track where we first met various people so we remember them more clearly I met them at so and so Place wherever like this this means adding a second Swift data model uh to our system we'll call it events and then linking events back to people before I do that some questions about the sort order parameter you need to write in a whole sentences here MOX ific otherwise I don't understand what you mean um if you're asking why we just pass in the whole value here I don't it's an array you can have more than one sort order in there if you want to um so you might have sort order by name then by age or by first met backed by age whatever I don't know um we just passed the whole array in but you can go to town add all the values you want to in there Brian asks how would I sort by last name you have to sort the first name and last name store them separately at different strings in the database and then combine them um please don't just use string interpolation because names are complex um particularly in Asian languages you often have last name then first name please use Foundation person name components for doing that but yeah sort the string separately and then sort on those things uh can you save sort default order um you probably could but have to use an enum Tyler I don't think I don't think uh sort descriptor works well with app storage or similar right now and so you want to back that out to enam you can control more easily hi Scott how you doing uh is sub because yes so as so no's asking why is subu rendering because when we change the string it recreates the people view struct every time uh and then that forces the query to be run again with a new value is being passed in uh stuff or Manuel asks why does edit person view preview not work like I said earlier I have disabled previews temporarily and I'll come back to them later on and add previews in most if not all play places I'm doing one lump R doing it in bit pieces everywhere Alex thank you so much thank you for coming along and donating um I'll do previews all in one lump at the end or near the end anyway uh Marco yes I'll be doing Cloud sinking towards the end it's going to happen uh Antonio show the person model there you go boom so that's our person model we're going to add to this a new model called event that will track individual events whether it's conferences meetups work things I don't know a variety events can happen around the world okay so press command n make a new Swift file and call this thing event. Swift again you want a swift data import and we're going to define a new at model class called event this will have a name String and location string where it took place plus that initializer made for us by xcode and that's a good start name of the event where it took place fine this time I want to add something extra I want each event to store exactly who we met the first time there this means giving the model an extra property to store all the people we first met there and so add extra property here V people is an array of person Jake yeah yeah you do you do yeah you have to Define initializers uh explicitly although X code just type I in enter it'll do it all for you these days it's much easier so this contains the people we met at that event we're also going to do the opposite we'll make each person know the event we first met them and so over in our person model I'll add a property here vet at an optional event now I've made this thing optional because it won't have a value initially users have to select what they met this person the fot will be unknown no idea where you met them please choose from a list of options if you want to I recommend you do you could extend the initializer to add that metat value inside there I can just delete it and make a new one I in boom like that it's nil by default which is great so that change means our connection goes both ways each person knows where we first met them and each event knows all people we met there both work both times uh we got a question is it possible to separate the same model container for different xcode build flavors yes you could use if uh hash if with some compiler flag being set according you want to and then it creates uh different containers if you want to it's fine Peter thank you for coming so we've just added a new property here at this point Swift data does three things that are really smart on our behalf first it sees these two model classes reference each other and so it creates a relationship between the two which means if we set the metat property of a person that person will automatically be added or removed from the appropriate people arrays in an event class somewhere Gary thank you so much that's the first thing it makes a relationship for us second because of that relationship what will happen is it'll automatically create all the database storage to handle event objects we haven't going to change our model container for person thing we're going to do for person and for event it knows person and event are attached somehow and so by just saying give me storage for parent will automatically give you storage for events as well and best of all it will do what's called a migration for us here it will automatically upgrade its existing database storage to add an empty value for where we met every person who was there already so even though we've just changed the way our class looks what it stores it will silly be upgraded to say okay now you can also remember where they were stored these all happen automatically which is quite beautiful David asks could we keep metat as non-optional I mean you could do um you got a bit of a problem we're not going to do it here but in the future if a person must be attached to an event then adding a person means going into an event first then adding from there to say where you met them so it's always a value for event but then in the future if you are to add deleting an event would you also delete other the people if so you can do that but it requires extra work this is the simplified approach where with an optional Swift will make two sides of the relationship for us automatically uh theme X digital says can you have a struct of something inside a class like a present with present a certain price yeah of course you can have structs inside class if you want to as long as they can form the codable they can be stored data if it's an array though um you're making a lot of work for yourself because they'll all be stored directly inside the underlying database record for your class it can make very very large uh rows in SQL light which is not a good idea you also lose the ability to do fine grain queering as well so I wouldn't recommend it if possible anyway focusing migration has been done for us adding and editing events is partly Cod scen already and partly new code first the easy part make a new Swifty y view that's that's Safari a new sfty y view here called edit event View this thing has to know what event it's editing so I'll say up here at bindable V event is an event and again it's going to break our preview and once more I'm going to say to you just comment at the preview we're going to return to this we will fix it previews will work just not yet let's build the app first and now we can fill in the body of our view up here and going say we have a form with a text field inside called name of event and it's text bound to Dollar event. name and then a text field called location with its text bound to Dollar event. location add a nav title of edit event and a navbar title display mode of dot inline that's the old stuff now it's time for the new work we need a way to connect someone to the event we first met them that means adding some extra code over in our edit person view this has to know all the list of events that exist show them all in a picker and actually attach those straight to the user and so inside here I'm going to add a new input for Swift data I'll also inside here add a query to read out all the events and here's where that array behavior of sorting becomes very very useful I'm going to say sort by event name first then sort by event location second so there's a tie it'll do name first then by location so I'll say at query the Sorting being an array we'll do sort the scriptor uh event do name and then sort the scriptor event. location this whole thing produce an events property which will contain an array of event like that I need a comma boom now this s order is not going to change dynamically it's just made once fixed forever it's fine it can be right here inside the proper definition now now when it comes to adding an event we're going to call an add event method that handles making a new event adding to the Swift data model context and then navigating to it for editing the code for that's going to come bit by bit in a minute but for now we can at least put in a little placeholder method method stub Funk ad event now for the UI of all this we're going to add a new section to the existing form using a picker to select from one of the existing events have made or have an unknown event option for the default value for new people we'll also use a section to add a button saying add a new event which just calls the ad event method so we can get to that screen easily while we're making new users and so before notes I'm going to say there's a section here called where did you meet them this has a picker inside I'll do met at with selection bound to Dollar person.at like I said the default value would be simply unknown event we haven't said where this person was met just yet but if the events array is not empty empty is false place a divider and then Loop over all the events give me one event in and show a text view of the events name then after the Picker we have button called add a new event with the action of ad event like that uh Tyler asks is there a way to make notes text field to show a minimum number of lines vertically uh if you wanted to does line limit not work I'd expect line limit works um yeah how T optional don't is in t optional to 100% to zero we optional values don't work great with bindings you don't want um optional bindings if possible Dave thanks for coming take care and so now are UI more or less in place okay we can now fill in the ad event method to create a new event and insert the model context so I'll say down here we have let event a new event again no name no location by default just empty on screen is good shows the placeholders and then we'll do model context here context do insert that event that's not going to work I think yeah I Haven actually made a model context yeah that's my mistake um so I'll say up here let's add that model context so at environment back slash do model context CRA uh V model context like that that should work read that so if you bu R incorrectly okay that now works correctly but there's a problem okay uh We've made the event we've told uh Swift to insert it go ahead and stall this thing but how can we trigger navigation to the event so users can edited now even if our edit person view had access somewhere up here to The Path property from content view we still couldn't place our new event inside there this thing is an event object our current uh path array is an array of person objects so we can't just put events in there it wouldn't work now Swifty y's got a solution for this and it's so easy to use we just modify one line of code the is called navigation path which is a way of storing multiple types of navigation destination in a single value if you're feeling technical this thing is a typer raised rapper around our navigation destination which means it can hold people events integers strings whatever you want to long as it conforms to the hashable protocol which all these objects from Swift data do automatically and so we'll change this little bit here it's not a rare person anymore it is a navigation path that's it we're going to change the way it's used down here because nav path already has an appen method attached to it and that solves one problem we can now push to both person and event objects now for the second problem how can we manipulate that navigation path from the edit person view now one easy option here is just to pass that navigation path into edit person view as a binding so we can change it directly this is a binding not a bindable we're referring to some external State being passed to us and so over in our edit person view here I'm going to say we have an binding for the navigation path navigation path like that actually have to bindable I think like that boom so give me the path to navigate to and now we'll send that in from our content view over here we send in the person and the navigation path of our path using dollar sign passing in The Binding to the path so we can changed inside there freely and with that being done our edit person view now has access to the same path being used using content view it can add things and remove things and have a nav stack update automatically and so back in add event down here hello dogs down here we can say navigation path append our new event like that that works great hello you are very hungry today dear me anyway so we're saying again make the new event addd and then navigate to it straight away now we haven't currently told Swift Y what to do when it's handed one of its objects it knows how to handle being given a person oh a person I'm going to show it a person view but now it's been given an event what should it do the answer is we want new navigation destination modifi that says when you're given an event please load our edit event view now you could put this wherever you want to I prefer to handle this inside edit person view because this is where navigation happens and so here I would say there's a navigation destination for event. self with the destination being event in edit event view event of event and that's most of the code required but before we see what's missing please go ahead and run the app now just try it out because you'll see the problem hopefully fairly quickly let's see uh I'm going go ahead and choose on rber here and as soon as I land on this edit person view you see a big red message down here the selection nil is invalid has not got an Associated tag as undefined results okay let's ignore that temporarily I'm going to press add a new event I'll type event in here I met this person at menion jump which is a big red dwarf conference at least it was for Co in Nottingham and it'll be here in our list I'm going to choose going to that conference and nothing happens so we have this big red message here and we have selection not actually working here both of these are linked to the same fundamental problem and both have the same fix we're going to attach tags to our pick of values to understands what the option refers to and so up here for our unknown event option this is going to have a tag of optional event. none and now we can change for each text to be tag of event so this is a a small but important piece of code we're saying when you choose this event the thing they chose was that event nothing else make that the selection being bound to met at and when I choose unknown event make that selection met at set to optional event. none now under the hood Swift optionals this thing here are implemented as an enum here it is it's generic with some kind of wrapped value which is fine and they're they're neat they're designed to some kind of value inside could be an integer string who knows what it's an event in this case here and you can see when we have our optional event that's a generic form of saying it's some kind of optional event but happens to be nothing right now because we can't just write nil here nil has no meaning by itself nil what what kind of nil is it a nil person is it a nil string a nil integer who knows what and so what's saying with this first tag is I am able to wrap some kind of event but right now it's empty it's nil in our current for reach down here we're providing the events themselves we say okay here's the one to load here's the one to actually show with a selection and what happen is when you run the code again it will silence the error message it'll go away now I can go to the view and boom no more big pick errors but when I change unknown event to be Dimension jump it still doesn't work this is where it's a very subtle distinction it really catches some folks out this isn't optional that happens to G nothing at all this tag here this event right now is saying I want to store the actual event for this selection whatever's there right now it's not enough because this is an optional event this is a non-optional event and this thing metat expects to be storing an optional event now we know this event exists that's why it's in the array please show me this thing right now it definitely exists but Swift data wants to be stored as an optional and so we've got to say this event right now the tag for it is optional event an optional of the event event that definitely always has a value but it's still optional and now when I press command R the code will work correctly I'll go in here I'll choose this thing and choose Dimension jump updates correctly so it's now working with our relationship smoothly it's a subtle distinction but it does matter very very much okay let's try and answer some questions did you ever struggle from p WM on high-end Max that's appalling cool um do I mind going to event. Swift of course go ahead there you go that's the code there uh what if met was an array of events uh then have a a many to many array it can do it too you'd want something more advanced maybe have like a multi-option picker which you can't do in 5 y currently you want to have a custom view basically selecting various boxes you could do it but it'll be uh more work uh you have can you have multi-level relationships I could make it work my app I had a parent Charlie Char you absolutely can it works fine I'm not sure where you went wrong um so you can have as many levels as you want to so a can have many B's B can have many C's C can have many D's and so forth all the way down um just be careful because you need to decide what's called a delete rule um when you delete your a do delete all the B's as well and all the C's and D's you to try to decide what gets deleted alongside it otherwise you can leave data floating around which isn't ideal really okay so now we've just added relationships we now have two lots of data people and events running here we're going to do previews next beforehand a reminder below the video link is a just giving URL this whole stream or my birthday is designed to raise money for folks experiencing homelessness please if you can donate some money at the link below the video uh Aiden asks does Swift relationship support relationship the same type yes you can do that absolutely fine that's one of the best bits about classes of course they can reference each other very nicely so managers can have managers can have managers can have manages all the way up if you want to that's absolutely fine um Anthony can we talk more about the Elite Cascade and how to structure it better um I'm not going to do it here we don't need at relationship at all here because we have an array on one side and an optional value on the other side Swift data can infer the relationship for us and is doing so correctly automatically it's very very nice if you want to change the delete rule then you'd have a relationship macro attached to it I have a whole free online book called Swift data by example that goes into detail on relationships there by all means check that out okay next up I've been ignoring previews intentionally and there's still more work I want to do in this application okay there's more I want to do but at this point I want to pause for a moment to resolve this outstanding issue how do we get xcodes previews to work well here this is one of the uh darker areas of Swift data we'll call it okay um because it does a lot of work behind the scenes and does so silently okay if I were in edit person view and I want bring this thing to life again let's un comment our preview okay um it's not going to work it's saying you got to pass a person in your very first attempt might be to say I'm going to make a person I'm going to say make a person here which is a new person the name actually fing for me the name of Dave liser email address Dave red dwarf. comom details empty string and then simply return edit person view with that person being passed in and the navigation path of let's just do a constant navigation path it's fine boom with the model container for person being passed into that is the I'm going to call it naive in a nice way that's the naive way of doing it and what you get is that you get a big problem report you get crash it's it is crashing the whole simulator at this point it doesn't know what it's doing it's very very annoyed you cannot do that and if you try and diagnose it'll tell you why um it failed to find a currently active container for person now this is obviously a problem because you think this is correct it's not correct because Swift data is being sneaky as soon as you call this person initializer here soon as you do that it will sightly look for come on then come on let them see you at least you get a treat um as soon as you call that personal initializer it will quietly and automatically look for whatever is the currently active model container okay and it does that to make sure everything's configured correctly in our preview code we are making the model container after we make the person object okay so there is no model container hence that error we can't find an active container right now and you're trying to make a person that's not allowed it'll just go bang and crash hard and fixing this means making the model container before we make our sample data but while we're there we also have to enable another configuration option we don't have to we're going to which is helpful which says to data this is previewing data only just stored in memory store it in Ram like a cache don't write to actual dis and so if you make changes to our previews they aren't being written to dis anywhere let's go into some virtual place in Ram and when the preview refreshes it gets thrown away straight away now solving this takes quite a few lines of code but because we're going to need it in more than one place we're going to carefully isolated out of the way in a new struct called a previewer this will have the job of set but setting up a default container and adding some previewable data so we can use that shared code whenever we want to have previewing done in our Swift UI views and so I going to press command n make another new Swift file here choose previewer do Swift for the name give an import for Swift data and then give it all the following code that's quite a bit first things first we'll say we've got a struct called previewer now this thing is going to work with swift data on its main context to insert data there into the main context and again that main context made for us by Swift data only runs on the main actor so we're going to tell Swift UI Swift sorry this whole struct also runs on the main actor so we can talk to that main context easily we'll make this thing have three properties propes we'll say there's a container which is a model container there's an event event and a person person we'll make three sample values for us to work with in our data so we can have them all here for reading elsewhere in our application we'll then make an initializer that can throw errors we make a custom configuration which is a type called Model configuration and this is where we can select is store in memory only do not actually write this data to disk I'll say true for that it's just temporary data for previewing purposes only we can then assign to our container try model container for person. self with that configuration that makes the container and it also makes the main context all in memory only and now we can make our event and make our person and Adam Swift data and it'll take care of the rest for us so we'll say our event is a new event named Dimension jump location Nottingham and our person is a new person named Dave list email address Dave at red dwarf. details empty string I'll add met at of our event and to end up we're going to add that person to Swift data so I'll say container main context insert the person now you can see I'm adding Dave list to Swift data I am not adding Dimension jump to it that is already linked from the person it'll add both for us automatically we add the person it'll also add the event for us so again the key points in this particular preview struct here it has to run in the main actor without that we cannot read this main context property easily it's complicated second we're saying store data in memory only as soon as the preview is reset clear the whole thing out from scratch be much happier don't have to keep on deleting things automatically just do it for us just wipe it for free making a model container is a throwing operation that's how I've made the whole thing throws so the person using previewer has to handle that somehow we make two pieces of sample data to us to work with uh and then insert one of them and again that relationship means both will be inserted and then we're storing the container the event and the person as properties here so we can read them externally I've made them all con and properties it makes no sense to change them once they've been created they're just for previewing purposes and with that in place we can now do previewing much more easily so back in our edit person view here I'm going to say let's get rid about all this code for now let's say that a do block we'll make our previewer by doing try making a previewer and send back our edit person view with the person being the previewer person and the nav path being the same thing before constant navigation path like that passing in the model container of our previewers container there's a slight difference in that model container uh modifier it's not model container for anymore it's just model container with some container then do a little catch block and send back otherwise text fail to create preview with error. localized description being passed in David asks if we change the event of the person will it automatically update the person's array of the old event and remove this person from it yes it will it's an automatic inferred relationship it's very very nice indeed so that is one preview working correctly now I can press uh option command p and hopefully see boom previewing now works very very smoothly uh we can do the same thing almost more or less I'll just copy the code in fact and put that into edit event view so over here replace the current code with that thing obviously now it's an edit event view edit person view and we'll pass in there's no path anymore to event really event is preview event like that otherwise they create preview as you can see boom preview works great now if you want to have good previews everywhere of course it works great there too so you go back to say uh content view for example uh very dull preview right now let's say instead make a new do block make a previewer inside there like that and send back a new content view with the model container of our previewer container and again catch with I'll copy paste that bit that bit boom that uh and so now we'll see an actual preview of data inside there you can see uh example agis in there in the list very nicely uh it's basically the same thing for uh our people view so I'll copy that go into people view paste on in here make a people view instead like that boom so at this point hopefully now all your previews should so show some meaningful example data correctly Dominic asks hi I was trying to make a watch OS app working on the same Swift Cloud containers OS apps as following the guides your site have has some Er the bundle ID uh no no it's nothing special at all it should work out of the box I haven't done watch for this project I did do um Mac OS and iPad and iOS together and all three synced beautifully okay so now we have previewing working correctly I'll say it again uh before we go to the next section here below the video is a just giving link today is my birthday I'm running this stream now an hour and 3/4s in specifically so we can raise money for folks experiencing homelessness or escaping domestic abuse if you can please contribute to the fundraiser below look for the just giving link below the video and give what you can because it is freezing outside it's a horrible horrible time to be out on the streets okay at this point we have a I think a pretty good app in place okay but there's another major feature I want to add I want us to be able to import photos of people they've met so they're easier to remember that's why it's called you know face facts right um and so uh Swift data actually handle this whole thing rather elegantly I think rather than storing Big Image Blobs of binary data inside our database we instead suggest to it to store these things as separate files and this reference the file name in the database so it's much much faster now we don't have to do any of that naming any of that referencing for us it's just done for us automatically by Swift data instead we just say hey Swift data I think this particular property would work best as external storage this is done with yet yet another macro it loves macros this one's called attribute and it's attached directly to the property you want to customize so in order to write images out to disk from our person here we're going to store them as optional data instances there could be a picture it might not be a picture so I'll say V photo is an optional data like I said we're going to use an attribute macro here to mark this thing as working best when stored externally so we'll say at attribute. external storage of our photo is an optional data so you can see we're now telling Swift data explicitly this thing would work best as external storage this is a suggestion internally Swift data can do whatever it thinks works best but honestly it doesn't matter to us we said it's a good idea if it honors it if it doesn't honor it it's not our problem we don't really care the whole storage system is effectively opaque to us we just don't care so now we have somewhere to store a person's photo we can build some UI around this to select uh that photo inside uh our edit user view if you ever used uh Swifty y's photos pick of view you'll know exactly how this is done but I'm going I'm going to walk through it here anyway uh we're going to say first things first let's add a new Import in edit person view for photos UI bring in that extra frame we need here we'll then add a new property here that will store the user selection this is a special type called a photo photo photos picker item when you select a picture it's not brought in straight away we get basically a URL to the picture not quite but basically here's where the picture exists in the photo library when we actually want to load that that's when data is copied across from photos into our app but this first thing the photos picker item is like just a reference to where the file actually is and so we'll say in here at State private VAR selected item is a photos picker item optional there isn't one by default and now we have that we can bind this uh State here to a photos pick a view which will take care of showing all the photo selection UI for us automatically what we're going to do is place a section somewhere in here with the UI I'll put this before the name I think it works best I say a section here with a photos picker inside the selection is bound to Dollar selected item I'm going do matching images we don't want to have like cinematic videos for example be it be weird or screen recordings I'll just do images and the label is going to be a label saying select the photo and system image of person like that uh David I can't quite read your question because YouTube puts a little heart Mark in its way uh will it place if we replace it in UI UI in UI I'm not sure you even mean sorry um if you if you choose a new image it will overwrite the old one that's what you're asking it won't keep multiple images around as you ask it to it'll keep only one around so that lets us select a picture here when it comes to handling selection again that photo pick a item value isn't the actual image data it's just a ref reference to it oh yeah it's over there somewhere we got to call load transferable type on that object to get the actual data of the object through this matters because imagine like a panorama they're very very very big pictures right they're huge amounts of data and so it's not instantly copied across to you it's done asynchronously when you're ready and so um we can have a separate method here in our edit person view that will call that load transferable type method and assign it to our person's photo and it'll do this on a separate task asynchronously so you can go to sleep for a while while work's being transferred while data is being transferred and assign it only when it's finished so down here and add a method called load photo let's make a new task that can work asynchronously in the background we're going to say here uh our our person's photo is try await the item they selected load transferable of type data. self now that alone isn't quite enough this code is likely to go to sleep for a while while it's being copied across from the photo library but when it's finally loaded and it's being assign to the person's photo that part will update our UI to show the picture and so it's really important we put this whole task on the main actor like that make this task run on the main actor so we change the photo from the main actor keep that thing safe Jacob asks do I have any thoughts on the composable architecture easy question no I don't this method load photo has to be called whenever our selected item changes they can choose picture or change their mind again and again and again much they want to here and so below the nav destination I'm going to add an onchange modifier that tracks onchange of selected item when that changes just call load photo like that so we have somewhere to St the picture we had a picker bound to that somewhere we got method to load the photo into our person's photo and we have an onchange to hook it all together at this point all the codes in place require to select and load a photo we're now try to put it on the screen somewhere now this contains raw data intentionally so we can read and write from dis smoothly Swift image VI has no native way of being loaded from image data so we going to bounce this thing through a UI Image First or an NS image if you're targeting Mac OS and so in our very first section up here before the photos picker I'm going to say if let image data equals person. photo do we have data here oops photo with a p like that if that is true do we also have the ability to make a UI image from that is this a valid image inside here and not like a zip file or something we'll do let UI image equals UI image data of that image data if both those are true we now have a valid UI image containing the person's picture we can place that inside a swift UI image I can say image UI image of that UI image I'll make it resizable and scaled to fit and that's it done that's all the code done we can now uh import a photo for a user and have it automatically saved an external file by Swift data here we've had to make no special accommodations for this thing being ex external storage it's just taken care of for us automatically now if you press command R and run your code again we just see it working I'll go to here choose select photo there's these six pictures It's rather dull um I have some pictures pre-prepared if I go to the photos app over here there's a default of Iceland pictures uh whatever whatever whatever um in my finder I'm going to people here we go these are my red dwarf people we have liser we have Rimer and we have the cat I'm going to drag those three into images like that so we can hopefully import them or at least import one of them oh there are all three AR R eventually to a a second to get there so now we have some meaningful pictures to work with not just pictures of Iceland even though Iceland's very nice and then uh back in our app over here I'm going to try again select a photo there we go I'm going to select uh Rimer there we go you can see it all sliden correctly and it's all being stored back and forward it's being saved correctly to the file it's all working very very very nicely uh theme X digital asks would it be much more worked open the camera to get picture yes it would vast amounts more work because annoyingly uh Swift data has a brilliant built-in way for choosing Swift sorry brilliant built-in way for choing pictures pictures from the photo library has not got a way to select pictures from the camera directly if you're using UI kit you'd use UI image pick controller which can do both in One controller swiftui only has half that function which is frustrating I have filed feedback asking for it I suggest you do the same thing if you want to do it you can do just wrap UI image pick controller in your own Swift UI UI uh view controller representable anyway now we have external data we have pictures being imported at another big check of our list done before we continue below the video link is a just giving link today is my birthday if you're enjoying the Stream dream if you're learning something along the way please donate to that just giving link I am raising money for folks who are experiencing homelessness or escaping domestic abuse right now they need all our help so if you can please support them with a donation in the link below uh so stuff Manuel saying don't forget by saying please in your feedback you're absolutely right if I launch my feedback here want my password uh there we go you'll see my feedback for this I think it follow it yesterday um blah blah blah submitted this one maybe nope this one there we are boom please add support you're nice thank you um I you know they're working very very hard and there aren't very many of them occasionally like extremely rarely maybe one in a thousand times I get bit snarky in my feedbacks but mostly I'm like please please thank you thank you I'm trying to be you know remembering the human exists anyway Tyler asks why are some photos put upside down that is so internally to to make you know less data loss basically pictures are stored in a particular orientation and they have a method to attach them saying now rotate to this orientation at runtime and so when when you rotate a picture normally it's just staying as it was the pixels aren't being removed at all to avoid you know jpeg recompression again and again and again to changed the option saying now it's clockwise now it's C clockwise whatever degrees like that right uh and I don't think Swift UI takes that value into account right now or as U likeit does it's not ideal that's my guess I don't actually know uh Michael no no special image Transformer here just make it data and and and go from there it's all exter as well it's very very nice okay we're not finished yet there's still more to do with now two hours and I'm going as fast as I can folks I'm really flying through it but with two hours in already there's one more massive job well job at least I want to do I want to add iCloud synchronization so we back up our data to iCloud so a user with an iPad and a iPhone and a watch and a Mac whatever um can all share the same data correctly it's an important important feature and it's actually fairly straightforward because Swift data takes care of almost all the work for us but there is a catch there's always a catch iCloud has special data requirements that go above and beyond what Swift data has and we got to follow those or our data will not sync to iCloud to get started you want to choose face fact at the very top here then choose the face fact Target option here then choose signing and capabilities we're going to add the capability of storing data in iCloud to do that you press plus capability search for cloud and select iCloud like that it'll have a little think now you need to check the box here saying cloudkit I want to store my Swift data information in cloudkit but you've also going to attach a container where in iCloud should it be stored you can see right now I have one already made out for face facts that's my bundle ID com. hacking with swift. facefa is my bundle ID prefixed by iCloud dot I'm going to check that now use that to store my data in iCloud you won't have one of these yet that's okay just press plus call it Cloud doyou bundle ID do whatever in there to make your own container for iCloud then check that either way you must have cloudkit and have a container checked here once you have that I want you to press plus capability again because we're going to add a second capability here called background modes so our app can do things in the background so press plus capability now choose background modes and enable the one here called remote notifications this means cloudkit icloud's platform can notify our app in the background oh by the way your data is changed so Swift data can start synchronizing data when the app isn't even visibly running so it stays updated automatically so you want to check Cloud kit and a container and the remote notifications background mode and that completes our configuration changes and so now I want you to try running the app again it's not going to work but let's run it again brace yourself iCloud and Cloud it I don't know why they just love love just spew logs into xod logs just vomits up textt glore to brace yourselfself Commander let's make this thing big so you can see all the logs spit out of me BL Stu yeah lots of logs and that's not even more it'll do more as actually actually working okay now the reason I'm saying this is because if you look at the top you'll see a bunch of big yellow messages here it's not happy with us these are the extra rules that cloud kit has that Swift data doesn't it's saying you cannot use cloudkit there a massive error now occurring because our classes don't follow Cloud kits rules specifically we must make sure that all our properties have a default value and also that all relationship relationships be marked as optional now in this app by no sheer coincidence this is actually very easy to do because for our person class here we can just say fine it's an empty string by default but all these things that's already nil by default it's an optional and then for our event class we can say the name empty string location empty string this thing is a relationship that has to be optional so I'll say you are an optional person array equal to an empty person array by default and that completely solves icloud's concerns now we have default value to all properties and our single relationship this one here is optional Mark cix asking do you need develop program for iCloud I believe you do I believe you do with those changes hopefully all the iCloud errors will now go away we're now able to store things in the cloud it's all loading correctly now there's no more big yellow messages log erors and stuff it's all happy this thing here is angry about some URL thing yeah that's just say it hasn't got an iCloud account in the simulator okay and on that note by the way on that note and honestly I've said this so many times and no matter how many times I say it people still don't listen to me it's very frustrating using iCloud in the simulator is a very bad idea okay you can do it you can log in there if you want to fine but it will regularly Miss behave or fail entirely the only way you can actually test iCloud well is on a physical device get your iPhone get your uh iPad whatever it is use physical devices assigned to iCloud to test iCloud syncing because if it works in the simulator it's basically luck it so often completely fails do not use iCloud syncing in the simulator because it just won't work most of the time now that's not to say that devices work all the time too okay very often the plague of developers devices is the dreaded cloudkit error 500 Apple's way of saying syncing just failed completely sorry and there's no way to fix it other than going to the iCloud dashboard and pressing uh reset environment for your container but that does indeed add full iCloud Support to our app so this Point we've got sorting we've got searching got relationships we got we got the uh Cloud kit we've got previewing we've got external storage and images being pulled in this is a good rounded app it's in a good place I'm taking it no further on this stream that was 2 hours and 10 minutes not bad um if you enjoyed the stream if you've learned something along the way please take a moment to click the link below to justgiving.com make a donation you'll see it in the Stream link below I'll try bring bring up again because it does matter uh where is it one of these billion things that's the one no let's edit your page B humbug one of these Pages here that's it there boom this thing here we've smashed the target thank you so much for generosity it's really really nice it's very kind of you folks there's still time to donate this fund rais is going to stay live for at least a year or something so if you're watching this video like in a six months time it's still good still enjoyed it please contribute something to help fight uh this problem we have which too many folks on the streets who need help who aren't getting help too many folks escaping de abuse domestically aren't getting help please help us fight that problem if you have questions of course I'm happy to stay around do my best to answer some um it is as a reminder my birthday I'd like to go and have some birthday cake along the way and have a nice time with my kids in the evening um but I do this every year so this is the fourth year now of the same thing building app from scratch with swift y and I know folks enjoy it very much and we raised lots of great money for charity too so uh it's a it's a good thing if you have questions please just go for it I'll do my best to answer them as we go uh Ryan um can you talk about about Swift data's flexibility can you create your own indexes right clal queries um so can you make indexes no you can't you ought to be able to um you certainly can Encore data I think Apple said this would come in the initial beta didn't land in time so it was yanked out before release I'd like to think that'll land at some point hopefully by 18 iOS 18 that is ideally before but yes be able to say ATT indexed is on the cards as a thing they're going to add I believe that' be very nice indeed to say yes this thing trust me this thing should be indexed would be nice indeed um can you run SQL querries no you can't as far as it's concerned there is no SQL here it's all just Swift data uh and so no um no I did not have my cake yet sorry uh yeah we try to find some sort of cake that the uh the dogs can enjoy you know tuna cake or something um you've got a crash and model container um that feels like you haven't got a model container in place when you're making a uh model object I don't knowly have to your code to diagnose more accurately uh David can you query Bas on relationships yes you can uh it's actually fairly powerful I did mention to you briefly that uh the hash pricate macro which we used in uh people view yeah people view here converts your code into a series of predicates let's a look at that how that looks if I expand this larger one you'll see it does some absolute magic here look at that it's so much work it's absolute genius Swift Code happening here anyway um you can and it's surprisingly the amount you can get away with um and annoyingly if you ever use chord data it used what we call stringly typed predicates your predicates were strings and uh they were obviously not safe you have to run the code see if it worked or not Swift data does compile time checking your predicates but occasionally just occasionally will still go wrong it isn't perfect it'll still crash at run times your predic bad uh so please check that one carefully any more questions Nick more of a web Deb here curious about iOS uh it seems like it's restricted just apple design oh yeah you can you can go to town Nick with custom design if you want to I'm not doing it here the problem with custom design is you know by all means do it I love good Custom Design the problem is that most folks who do it don't stop and take the time to make sure that design works well for everyone so for example all of Apple's controls the list the navbar the tabs the buttons you name it the every single one have been polished and Polished and Polished again to handle things like Dynamic type where the font gets bigger or smaller according to user settings to handle things like voice off support So us just can swipe the way through and access the entire app even they're completely blind and a lot of folks go for like oh it's gorgeous design straight from like dribble whatever and it's completely in inacessible to voice over like a blind person can no longer use the app this happens very very painfully often just just this last year this year this year Blue Sky the fancy new Social Network managed to launch several months with zero voiceover support it was completely impossible to use with voiceover um they used react native for I'm not going to get into why it's good or bad but it meant they weren't using Apple's native controls anymore and so it's very very hard to use for folks who couldn't see and so yes you can go to town with Gorgeous Custom Designs but please take into account people who need extra support for things like uh color blindness or font size or whatever uh Aiden so you can customize it actually as a sorry the question from Aiden can you customize a searchable placement yes you can uh when you're saying searchable here here uh you can say placement boom right there and customize how it looks so I might say this thing bind search text with placement of one of these so the sidebar the toolbar you choose it um that is an option you can choose if you want to uh David can I do some some more work uh I me it's two hours and a quarter I've got stacks of examples just Google up Swift data by example you'll find stacks of examples of ration chips and queries in there um please use that um I'm not do anymore at this point uh Sirus cat do I see any benefit to adding swi to apps that reducing Firebase or a similar Cloud DB um I get folks often asking should I migrate my core data code to Swift data uh and I answer the same way I answer the same question when it came around years ago should I migrate my objective c code to Swift uh no you really shouldn't because you have a great hopefully shipping app that works well it's tested it's it's bug free or more or less it works well if you rewrite that in Swifty y or in this case Swift data the best result you'll get is is exactly the same thing you had before the worst result will be all the same bugs you had before plus a whole bunch new bugs in your translation layer copying things to and frow whatever uh can get quite messy and so broadly speaking if you've got a great system already whether that is I don't know fire base or whatever um if it works for you use it I'm I'm not some like Zealot he say you must use Swift data I I hate those kind of pointless Petty Wars use what works well for you for me that's Swift data if that's Firebase for you you do you you rock on I love it internet high five Anthony yes accessibility is important I can't say it it's one of the few words I can't say in a sentence accessibility U but it is critically important and I will always always fight for it any more questions before I go and leave you all free to rewind and figure out what I was saying halfway through uh does Swift data provide thread safety yes uh use it carefully and I I do explain this more in the book Swift data by example which which is free online by the way Swift data by example it's all free online uh it's it's it's built with swift concurrency mind which means things like uh model instances like an instance of a person or a model context cannot be passed around from task to task they are not thread safe they're not sendable okay a model container the underlying database storage is sendable it is safe to be passed around to another actor for example and so if you wanted a background actor doing some work in its own model context over here somewhere you would pass in in the model container and make a local model context on the other actor and that would be safe um don't try and pass into the context that won't work very well uh everyone go and buy a book for Christmas yes please buy a book for Christmas that a great gift to yourself uh will I make a video says just smile for folks who struggle to choose do web or RS no I won't choose whatever you want to make and have fun with it I know I love building web stuff I love building IB stuff I love just building stuff go and do whatever makes you very very happy and I will be right behind you uh the dip Mass are you interested in working on a time machine sure yeah come and meet me yesterday and with your time machine and I'll I'll discuss it with you if you can't do that doesn't exist um jug Q if you embed an mtk view in 50y the 50y views will front of I don't know I I don't have access to Swift UI source code folks I have no idea what it's doing behind the scenes uh you go ahead and and talk to Apple directly uh you know file a DTS report DTS is great for that talk to Greg and the DTs team he does metal um and he can help you out he's very nice Nick says Paul with custom views can you completely roll your in custom interface you can do whatever you like you can Nick you can go to Absolute town and draw what ever you want in any way you like it is extraordinarily flexible there are some gorgeous Swift UI interfaces out there built completely from scratch their own shapes own designs own buttons whatever it's down to you just please please if you remember nothing else make it accessible to people who can't otherwise use the app like switch control for example Anthony just learned about swifted by example folks it's it's really really good you want to go to hacking with swift.com quick start Swift data it's free it's massive contains lots and lots of questions about Swift data all answered really really quickly and easily so please do uh check that out Eric your most welcome uh to our egg uh what were the reason between images behaving better in a horizontal H stack while a Lazy H stack the bit an off topic question there um t94 um a lazy stack has no idea how big its content are going to be are they or small or big we don't know okay and so it automatically stretches to fill the screen it fills all the available space to play it safe otherwise it might start small for a small image and a bigger one Scrolls in it kind of grows to be bigger whatever it be very annoying right and so it's treasured to fill the space a regular H doesn't do that and so uh that's a different maybe it's causing that I'm not sure otherwise Miguel what's my opinion on the Singleton pattern now we have observable macro working so well in performant um you know the app model is what you're calling it really a single app model rather a view model that controls a whole app is now obviously feasible it was a massive performance trap before because changing one view would change every view uh be a bad idea it's not really a Singleton um it's just a a single instance of an object that happens to be made just once but it could be made more than once it's fine go for it I think that's a great solution these days thanks to observable um Singletons themselves forcing Singletons can cause testing problems so I'll be reticent to go down Singleton unless it was the right solution so be careful just smile the Market's hard it is hard everywhere's hard we are in a recession still worldwide and so it's difficult uh Marco yes there'll be a GitHub repo tomorrow probably when I've had my keg okay that is now 7:20 UK time um if you have last minute questions now is your chance uh please do ask them otherwise otherwise I'm going to skidadle and watch probably home alone because it's almost Christmas time with my kids last chance any more questions occasionally staff you press command R it refreshes the stream and fix the problem uh it's kind of hitting Miss sometimes streaming it's hard who knew okay that's it for me folks again below the video just giving link please donate what you can uh it's a really important local charity to do some fantastic fantastic work if you enjoyed the video if you learn something along the way please donate it makes a big big difference even just a few bucks or Pounds or Euros whatever it really really helps every little bit helps at this point cuz it's very very cold and these folks need our support if you have further questions you can try asking them but I'm be somewhere else so you might use Twitter um otherwise I won't see them the source code will be online on GitHub at some point in the future hopefully tomorrow and the video will stay here on YouTube for rewatching as much as you want to and of course you can add comments below uh please do leave a like or subscribe all that kind of you know YouTube jazz it all helps the algorithm slightly and I am unlikely to see you again folks between now and Christmas so I hope you have a wonderful wonderful break if you don't celebrate at least have time off watch TV play video games if you do celebrate I hope you have a lovely lovely time with your family and get all the gifts you want take care folks it's been a pleasure having you all take care