Episode #571

Editing Relationships with SwiftData

Series: Leveraging SwiftData for Persistence

21 minutes
Published on January 16, 2024

This video is only available to subscribers. Get access to this video and 582 others.

In this episode we'll create a form to add and edit songs for a given artist. This will lean on the technique we used last time with a small workaround required.

This episode uses Swift 5.9, Xcode 15.2.

In this episode we're going to take our Swift Data example and we're going to

build out the ability to edit songs for each artist. Now a lot of this is going

to be similar to the last episode but there's some tricky points which I ran

into and I think they'll be valuable to cover in this episode. So to get started

we have our artists view and here we have the ability to tap on a given

artist to edit it but I want to be able to tap on an artist to go to the detail

for that artist which will show the list of songs. And so we're gonna have to

change this up a little bit. We are going to wrap this in a navigation link and a

navigation link will have a value and a label. So the value will be the artist

and the label will be the content that we put inside of here. And we can just

move the text inside and that means we don't actually need any of these things

here because tapping on the entirety of this view will will work so we don't

need the content shape or the max width frame. And we actually end up breaking

the ability to edit this so we're gonna add some swipe actions so each row has

the ability to configure stuff for swiping. So first we're gonna add a

button here who has an action of editing artist equals the artist that we're on

and then has a label and then a system image of pencil. Then this will also

override this delete behavior so we actually just need to move that delete

behavior into a button as well. So here we will add button whose action is going

to be deleting the artists that we are in and that actually becomes a little

bit simpler because we already have the artist. And then the label

for this one is going to be delete and the system image of trash. And then we

can delete this. So let's go ahead and run this and make sure that we still

have the ability to edit and delete an artist. So I'm gonna go over here I'm

going to do that and look at this right here we want this actually to have a

role of destructive so that so that the color of that button becomes a little

bit more obvious what's happening so that we can delete an artist and that

works. Okay and we can also hit the pencil to edit the artist and that works

as well. So now when we tap on it we need to be able to go into the the form for

this. Before we do that I want to address this little button here it says person

dot add and that is actually not a valid symbol so let's go ahead and look at SF

symbols and we'll say person dot add and it looks like we have person dot badge

dot plus which is what I intended there badge dot plus and that will fix that up.

Okay so now we need to create a destination for this to go to. To do that

we're going to create a new file here called artist songs view. Now this is

going to have an environment value for the model context. It'll also have the

artists that we are going to be looking at and then a state var for the the

editing song which we'll get to in a little bit but for now we can just make

that nil. Then we need a body here. Now the body is similar to previous examples

I want to sort of whittle down the dependencies and so I actually want to

create a songs view that takes the list of songs from the artist songs property

like that and by doing that it allows us to use this in more than one place not

just the songs for one artist but we may be able to look at songs for you know

recently added songs across all artists or whatever. We have the ability to do

that and then we're going to pass in the editing song as a binding which will be

dollar editing song and so let's create that down here we'll have a struct

songs view for now we can make it private which will be a view and then

this one will have the songs which will be an array of song and it'll have a

binding to the editing song like that and then we'll have a body here and the

implementation here is going to be pretty simple we're gonna have a list of all

the songs those songs are already identifiable so we can just do a simple

one like this we'll have a text for the song title and I want to be able to tap

on this we're gonna use that trick that we saw last time for max width and

alignment to go to infinity and the alignment will be leading and then a

content shape of rectangle so we can just tap anywhere in the cell and then

when we do the on tap gesture we can set the editing song equal to the song okay

so now we have our songs view and we're passing that along here now we don't

have a preview for this just yet because our artists don't have songs they will

in my case because I've been working on this example already but in your case

before we enter the the ability to edit a song it's important for us to check

our work and so I'm going to create a container here we're gonna do try model

contexts sorry mark model container for artists self and we will initialize a

configuration is stored in memory only is true then we'll create a context

which is going to be model context for this container then we're going to

create an artist which will be an artist whose name is Pink Floyd then we're

going to create some songs so we can just say artist songs equals like this

and then we can create a song and for now we're just going to pass in the

the title and I'm gonna go into our song model here and I'm gonna make artists

optional and the reason why I want to do that is that the artist being optional

is important for cloud kit because when you have relationships between objects

and cloud kit the synchronization of those records arriving at different

devices may come at different times so they will eventually settle on records

that point to each other correctly but it's possible for a song to come in

before the artist comes in for instance if it came if these changes came from

another device and so those things will end up being nil in that case and this

is probably just a good habit to get into because you don't want to crash or

to be unable to import something if its associated record isn't there yet so if

we do that then we should be able to just say the songs title is say hey you

and we'll do a couple of others let's do money and breeze okay so we've got some

artists and songs now we will insert that model into the context and now we

can return a songs view with those songs we can pass in artist songs and the

editing song will be just a constant nil okay I'm missing the import for Swift

data and let's see okay let's take a look at our preview

and now we can see that we have our three songs that we created so now that

we have that we can finish out the ability to create new songs so we're

gonna go up to the artists songs view here we can add a navigation title of

artist name which will be useful later

and now let's create a form so that we can tap on the songs and edit them

similarly to how we did the artists last time and similar to last time we're

going to have a song form content view that will make it easier for us to sort

of whittle down the dependencies here so we're gonna have a song form content and

we will have a bindable of our song is a song and I believe that that's all we

need there then we can have a form inside of here we can have a text field

for the title and this one will be a dollar song title okay so that means

that our song form up here is going to have some of the environment stuff that

we had before so we're gonna have a dismiss environment value dismiss we're

also going to have a private let model context so this will be our scratch

context that we create then we will also have a private let artist and then a

bindable var song which is a song okay so inside of this because we're doing it

this way we're going to have to create our own custom initializer for this data

so we can get rid of this part of it we will need to have the artist and the

song and then the model container container is going to be a model

container okay so what we're gonna do here is we're going to create a new

context so we're gonna say let context is equal to a new model context in that

container we're going to set autosaved enabled equals to false and then for the

artist instead of referencing this artist directly we're gonna say given

this scratch context let's load the artist again so we're gonna say model

for we're gonna use artist ID and then we know that that's going to be an

artist so we can force cast it now if this song is a new record then we're

gonna do one thing so we can just assign it like this because it doesn't exist in

any context otherwise it's not a new record and we can load it up from the

store from the using the new context so we'll say model for and we'll say song

ID like this as song and then finally we can set model context equals to that

context okay so for this value here we'll say song song and I think that we

can just make this private private let song because we don't actually need it

to be bindable within this UI okay so here I want to create a toolbar then

we'll create a toolbar item with a placement of top bar leading and this

will be a button whose role is cancel

we'll call the dismiss action and then the label for this is going to be a text

of cancel then we will do a toolbar item for the placement here we'll do top bar

trailing we'll have a button calls save changes which is a method we'll write in

a minute then we'll also call dismiss and then we'll have a label whose value

is safe okay let's write our save changes actually before I do that I want

this button style to be bordered prominent and then for the whole thing

we can add a navigation title of whether the song is a new record if it is we'll

say a new song otherwise we will say edit song okay now we can write our

private funk save changes we're gonna set the songs artist property to be the

artist that we loaded then if the song is a new record then we need to insert

it and we can do that by saying artist songs dot append song and we know that

the song or rather the artist is already added to a context so when we save the

context it'll save the artist because it changed thereby inserting the new song

and then we can say try model context save now what's gonna happen here is

because we have changed the artist or because we've changed the the song song

gets created if the song is a new record it will get inserted into the artist's

array thereby updating the artist so the idea here is we don't actually need to

insert anything because we got it from here we got it from here and here if it

was an existing record and if it was a new record then we just assign a song

but then it gets added to the artist so it'll get saved accordingly here so in

both cases for new records and existing records there is a way a path for the

song to get inserted into the context so then we save our changes okay so let's

go back over to artist songs view and this is where we had the navigation link

sorry that was an artist view we have the navigation link that has a value of

artists but we haven't told this where that value should go so before our

toolbar but after our list we'll say navigation destination for and we'll

pass in artist self and the destination is going to be the art is going to pass

in the artist and we have to pass in a destination so we can say artist songs

view passing in the artist like that okay so now in our preview here I'm

gonna be previewing just the song form content this actually isn't super useful

because we know what this is gonna look like but this is kind of why I wanted to

do that is so that we have an easy way to just preview the form without all the

surrounding stuff but for now I'm just gonna delete that we need to have the

toolbar that will let us add a new song so let's look at artist songs view here

we're going to add a toolbar to create a new song and set the editing song

property to that song and then we also need a way to show the form and we will

do that as well in a similar fashion when the editing song changes to some

value we'll create a navigation stack we'll create our song view with the

artist song and container and then we'll set the presentation detents similar to

how we did before okay so now we go over here if I tap on this now I can create a

new song so I will say hey you I will click Save and we see our first problem

our UI is not updating if I go back one step and in again we can see that I do

have that value and if I tap on a song to edit it and hit save we have a

similar problem so this is actually a bit tricky and it took me quite a long

time to wrap my head around what's happening and what we can do to get

around it so if you follow most of the tutorials out there they're gonna say

that you should just pass the main context along when you're editing so in

our song form this sort of trick where you're creating a scratch context this

is something I want to keep this is a pattern that we've used in core data in

the past where you can kick off a you know workflow or series of screens that

can edit some stuff but those those say changes will not be saved until the

users hit save another option would be to use the main context but then say

something like context dot rollback this seemingly did not work for me and I'm

not sure if you have to do this within your own transaction I did experiment

with that and did not see it working but I also don't like the the sort of notion

that until you hit save there may be some sort of you know I don't want to

rely on hitting the cancel button to roll back changes so that's another

thing that I did not really care for so if we think about what's happening here

we have this artists songs view and this artist songs view has an artist that has

songs being passed into songs view and songs view is not getting any indication

that it needs to update now the way that I have decided that I'm going to fix this

there there are a number of ways that you could explore one of them is on

receive and you can use notifications Center default get a publisher for the

NS NS managed object context did save this actually does throw even this is a

core data notification because we know that Swift data is using a lot of the

underpinnings from core data these these will fire but this isn't really what I

want to do and I found that timing was also an issue here and so what I decided

to experiment with is just forcing this to reload by giving it an ID that

changes when when the state of our view changes and we know that the state of

our view changes when the editing song changes so when you start editing a song

it changes to editing that song and when you stop editing a song and these are we

render as well so here if I go over to say money and I delete the two and I hit

save now it has forced this view to reload now this I still view view this

is sort of a hack and if somebody else has a proper explanation for why the

first why using this sort of pattern works in the first example when we're

just modifying the artist versus the second when we're modifying the sign of

the song that would be great but for now I think that this is doing what we

wanted to do we can add new songs we can edit songs we haven't implemented swipe

to delete but it's the same thing that we've have seen here so that's how we

can edit insert and edit relationships for an object in Swift data.