Dynamically filtering @FetchRequest with SwiftUI – Core Data SwiftUI Tutorial 6/7

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] one of the swift ui questions i've been asked more than any other is this how do i create a fetch request with a dynamic filtering or sort order and this question arises because our fetch requests are stored as properties on our swift ui views and so if we try and make those depend on other properties like a sort order or a filter for example then swift will complain now there's a pretty simple solution here and i think honestly it's obvious in retrospect because it works pretty much like how the rest of 50 ui works we want to try and carve out some functionality into a different swift ui view then inject values into it and i want to show it to you with some simple example code so i've put together what i think is the simplest possible example we can we'll add three singers to chord data then use two buttons to show either the ones that start with a or the ones that start with s and so in your core data model start by making a new entity in here called singer then give it two attributes one will be first name one will be last name both should be strings so string here and string here now i'd go ahead and bring up the data model inspector with singer selected and change code gen from class definition to manual slash none we'll make it ourselves and then go to the editor menu and choose create ns manage object subclass for that next and that one next and then put it inside the correct folder like that so now we've got a little singer class we can customize we want to and once we've done that i'm going to open up the core data properties.swift file and add two properties to make it easier to control the string optionals one will be wrapped first name a string and i'll send back first name nil coalescing unknown our second one will be wrapped last name string and again it'd be last name no coalescing unknown we might of course get the famous singer oh no no no let's hope not anyway on to the real work the first step is to design a view that will host our information like i said this is gonna have two buttons uh show me a or show me s to control the way it's filtered show you a single singers but we'll have an extra one to insert some example data so we can see it all working correctly so we already have in here our import record data plus our manage object context we want to work with and i'll just get rid of this sidebar here and print the canvas it's going to play ball perhaps we'll see there we go um we're going to track in here the current sorting so i'll say at state private var last name filter is a showing the only the singers whose last name starts with a now for the body of the view we're going to use a v stack with three buttons plus a comment for where we're going to place our list shortly so we'll do a v stack here then a list of matching singers then our button saying add examples and this will say go ahead and create some singers so i'll say let taylor is a singer in the context of our manage object context taylor.firstname is taylor taylor.lastname is swift i will uh copy and paste a little bit so we'll add taylor we'll add ed and we'll add adele so we'll say ed's here and then here and then here adele is here and then here and then here so taylor's correct ed's first name will be ed his last name will be sheeran and adele is adele and adkins like that and then once we're done adding all our objects like that we'll go ahead and call try mock.save to write it out so adds our examples and now we're going to add two more buttons one to show only the a names so in this case adkins and one to show on the s name so swift and sheeran so we'll say below that is a button show a last name filter is a and then button show s last name filter is s now so far this has been pretty straightforward but now for the interesting part we're going to try and replace this list of matching singers here with something real and we're not going to use at fetch request here because we've got to be able to make this thing inside initializer somehow to customize it because we're changing the state value here right and that last name filter will affect our fetch request so we cannot make a fetch request up here that would make one property depend on another property which is not allowed in swift so we can't do that so instead we'll make another swifty y view to handle this filtered list so press command n choose 50 ui view and then call this thing filtered list like this and then create now this thing will be the one that actually handles our effect request and it'll work because it'll pass in the manage object context from the previous view it belongs to the previous view so it's fine so we'll say in here there's a at fetch request var fetch request is a fetched results of singer and that stores our fact request it does not create the fetch request we're making it here so we can use it later on but we're not making it here because we still don't know at this point what we're searching for and who we're searching for instead we're going to make a custom initializer in this view that accepts a filter string look for a look for s whatever and that sets fetch request be the right thing so go ahead and add this initializer now init with a filter string underscore fetch request is equal to fetch request here of singer with sort descriptors of an empty array and a predicate will be ns predicate with a format of last name begins with one word begins with percent at then our filter whatever the string have asked for begins with s begins with a or who knows what and that's going to create the fetch request using the current managed object context remember this view is using the side content view so it'll get the data from there it'll get its managed object context it'll do the right thing automatically we're going to inject one by hand in here it'll get the one from content view but the important bit here is that one character on line 18. there's an underscore at a start of fetch request and that's intentional so we're not writing to the fetch result object that's inside our fetch request we're instead writing a wholly new fetch request now to understand this think about previously if we had an at state property here uh called private var score is zero whatever right we'd have a a state value here with a score entering the side and if we said score equals 10 we understand put that that 10 value into the score integer but behind the scenes this whole thing the score is actually stored inside a different struct called state this thing here this property wrapper wraps up the integer in a new type called state and that's the one that handles watching for changes and re-invoking the body property and all that kind of nice stuff integers can't do that the state struct does that in fact right click on here and ask the jump definition and see it in action uh these are all unhelpful thanks fdui let's try and state in here perhaps might get lucky there we go you can see an action as a struct in the swift ui interface if you want to it's just there there you go struct state store some kind of value and this means it reloads the body when it changes so when we say uh square equals 10 we're writing the modifier we're saying hey put this 10 into the integer right we can also write to underscore store uh score sorry underscore score that means i want to touch the state object directly the state wrapper itself i want to make a new wrapper around a different integer i suppose just changing the current integer the same wrapper anyway by signing here to underscore effect request we aren't saying here's some new results to load like we're not giving it new results to show we're saying change the whole fetch request itself now at this point all remains is to write the body property so we'll say there's a list of our fret request the id of backslash dot self one singer coming in we'll do text and then uh quotes okay it's right singer dot wrapped first name first name like that and then singer got wrapped last name like that so show taylor swift dale adkins ed sheeran in each row like that as the preview you can just zap that it's not required here okay so now we have our little filtered list view which understands give me a filter string and i'll do the check for you and load back the correct data and now back in content view we can go ahead and use that for our list of matching singers we can say show a filtered list here with a filter being our last name filter so it's a right now but it could be s in the future go ahead and press command r to build and run your code and give it a try make sure you start by pressing add examples like that we're only seeing a dell right now because the default value of filter is a but change a to b s and you'll see it jumps between the two correctly so it's the same ultimate view showing the list but the filter inside is changing dynamically now so it took a little bit of new knowledge to make this work right you have to know how to use the underscore for example to poke around in the actual fetch request object not the fetched results inside it but it really wasn't that hard and as long as you think like 50 y you're most of the way there now you might look at this code and think well wait a minute this initializer is run every time the view is being recreated right which is every time any state changes right whenever we change with a and s a and s ans that code will run okay but that also in turn means well we're changing the fetch request every time as well from a to s and a to s and data s which seems uh disastrous because that would mean we're reloading the data every time we're changing any state in our view there's some other thing happening like a counter changing one two three four five unrelated to our filtered list it'd be reloading our data every single time and that might sound wasteful and honestly it would be wasteful if it actually happened fortunately coordinator will not do that it'll do nothing like that it'll only actually rerun the choreo request if that state has changed it's smart enough to know aha i've got that already and just put it on through even if the view is being recreated as long as the actual sort filter hasn't so the predicate filter hasn't changed it won't change it now at this point you know everything you need to know but if you want to go further for more flexibility we can actually improve our filtered list view so it works with any kind of entity because right now it works with singers and actually you might want to use this elsewhere in your code for any kind of data to do this we can no longer refer to singer here we're going to use generics with a constraint saying you can give me any kind of object as long as it's some kind of ns managed object because that way cortex can use it we'll then have a second parameter inside our initializer here to decide which key name we want the filter on so we can't always use last name here that doesn't work right if it was starships or chocolate bars or who knows what last name wouldn't make sense we can't always use last name and now because we don't know what each entity is going to contain we'll let our parent view our container view decide so we could no longer have show a text of rap first name wrap last name here we'll just say hey uh whoever contained or control controlled me or contained me created me that's the word whoever created me you tell me what to be in each row and i'll just say here you go here's a singer what do you want to do so it'll do the right thing automatically there are two complex parts in there which is why you don't have to be here you can stop the video skip on to the next one and you're perfectly happy but there are two complex parts here first is uh the closure that will replace this code here right they'll say what should be each row is complicated because it's got to use two important pieces of swift syntax you haven't seen before um we've seen them i think briefly previously but only like in passing uh one is at view builder which lets our containing view send in multiple views if it wants to um you know here it's one but it couldn't theoretically who knows what it's down to containing views of desired the second one is uh at escaping we're going to say the closure you give us won't be used now it'll escape our initializer and be used later on in our body which is why it's escaping um that's part of it the second complex part is how we let our views customize our filter key because right now we have last name it begins with uh percent at right so you might say um okay let's take an educated guess here and say uh percent at begins with percent at and then use something like a key name and filter um but that's not going to work um to find keynoting for a start but we'll do in a second obviously it won't work because when you use percent at coordinator automatically inserts quote marks for us so it reads correctly remember begins with s or universe equals star wars the quote marks it does that for us when we have percent at so we can't use that here and so would end up with something like you know last name equals either whatever um which is not what we want like that bits fine this bit is not fine so we can't do that so the attribute name should not be in quotes and so percent at is not the right solution here the actual solution again you you couldn't guess this this is just how it works uh is called percent k percent k and that will insert the values we provide still insert key name in this case but it won't add quote marks around it okay let's go ahead and undo it for a second and go to where we were when it all worked there we go okay let's increase our upgrades here we'll say our filtered list thing here is now generic over some kind of t but it must be an ns managed object now you've got to insert your core data here making sure it's happy some kind of match object but also the content of our list the rows will be some kind of view content so we'll say you're gonna get some kind of content but whatever that is for the row it's got to be some kind of swift ui view right so you've got to give me a managed object to work with and a swift ui view to layout inside your uh list rows inside our thing we have the fetch request here by request fetch results we don't want to use singer anymore we want to use whatever they asked for whatever this t was up here whatever managed object they want to work with that's the one so we'll just put in t here next up this content for our list rows what do they want to show in there now we know it's going to be some kind of content which is whatever view they've passed in so we'll say in here you've got to say give me some content closure which will accept a t and return some content so we're saying i will give you as a parameter to your content closure whatever is your tea here's a singer here's a chocolate bar here's a employee here's a starship whatever it is whatever you've made this with i'll give you one of those and it's your job to give me back some content to show inside my list and that again must be some kind of swift ui view so i'll give you a t give me back some content like that inside our list here we're going to list request id self one item comes in we don't know what it is anymore we can now call our content closure passing in that item so you've given me one singer one chocolate bar whatever i can then pass that to the function you provided earlier this content closure here's the item you asked for boom here's one singer give me back the correct thing to lay out and that's what this code is doing right here now we're going to upgrade our initializer here so we'll have our filter key as a string now and also our filter value is a string now that's easy part the harder part is how we pass in this content now remember at its simplest we would say this content is a function that accepts a t and returns some content that's the easy version and we can just do you know self dot content equals content but that's not going to work because we uh it's actually one straight away um this is a non-escaping parameter assigned to an escaping closure so what's saying here is we're trying to stash away this function for later on to be used up here we haven't told swift that and swift by default assumes a user closure immediately inside the initializer so it can not have to wire up memory management it does an immediate call and move on we're stashing it away keep this alive for me for later on with what was saying we're going to tell swift that we've got to say actually this thing will be an at escaping function it's going to be used later on not now and like i said the finishing touch here is to also use the at view builder attribute which is what most swifty y uses things like v stack and lists and all the other types here and they mean you can provide many views in here if you want to and we'll do the right thing you'll get tuple view of zero one two three whenever you want to so a viewbuilder here is a finishing touch now for our fetch request we don't want to have a fetch request a singer anymore because it's not singers we'll do a fetch request of t so whatever type of answers to work with then the sort hasn't changed the predicate though we don't want to use last name here to the percent at we want to do percent k begins with percent at and now pass in both filter key and filter value like that and now with that in place we can go back to our content view and use the new filtered list so filtered list now we're going to provide a filter key we'll say give me last name and filter value will say our last name filter but we're now going to provide a trailing closure it will give us one object back what do you want to do with it so we could say i want to have let's hide this left hand bar make some space i want to say i want how the text of um singer dot wrapped first name and then uh singer dot wrapped last name like that but where did singer come from and this is passed into us remember we could just say sing it in it'll pass us one object from the list that's our choice of how to decide what we do with it however that code is not going to work as you can see it's complaining it says you've got an ns manage object back not a singer now obviously singer our entity type that's made for us comes from ns managed object but we have to tell swift that we have to say this singer is actually going to be a singer class it's going to know that so to say this thing here you expect one singer coming in that's what you'll get and that's what makes the whole of our code work because now it knows it's a singer this t becomes singer therefore t here becomes singer t here becomes singer t here becomes singer everything else becomes a singer it knows our content closure must accept a singer object and that's why we can read rap first name and wrapped last name so that extra information singer is a singer is what tells swift yeah this is how filtered list is being used let me access the data as it were a singer here now remember this can handle any kind of ns managed objects on your own code you can reuse filtered list to handle any other kind of data now it's really really flexible it'll handle any car filtering any kind of filter key it's really nice anyway that in place press command r if you want to it should give us exact the same result so there's a dell and there is a taylor and ed it has actually changed but it's much more flexible and therefore much more useful too you
Info
Channel: Paul Hudson
Views: 826
Rating: undefined out of 5
Keywords: xcode, swift, swiftui, ui, ios, programming, tutorial, example, project, code, uikit
Id: O4043RVjCGU
Channel Id: undefined
Length: 23min 24sec (1404 seconds)
Published: Sat Nov 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.