Introducing MVVM into your SwiftUI project – Bucket List SwiftUI Tutorial 11/12

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] so far i've introduced you to a range of swift and swift ui concepts i've also given you a number of tips to help you write better code and help keep your code better organized here i want to focus on that last part more clearly i want to look at what's commonly called a software architecture or in a more grandiose way an architectural design pattern really it's just a particular way of organizing your code the pattern we're going to look at is called mvvm which is an acronym for model view v model that last part is just one word ish view model and this is a terrifically bad name and it's really really confusing for folks learning it but i'm afraid we're kind of stuck with at this point it's been around for years so it is what it is um there is no single definition of what mbvm actually means and you'll find all sorts of people arguing about it online but that's fine here we're going to keep it simple we'll use mbvm as a way of getting from our program state and our logic out of our swift ui view structs we are in effect separating logic from layout now we'll explore the definition a bit more as we go but for now let's start with the big stuff go ahead and press command n make a new swift file and call this thing content view dash viewmodel.swift this thing has to have an import for map kit so we can use things like map regions and coordinates are similar and we're going to use this thing to make a new class that manages our data manipulating on behalf of content view that's its job so ultimately our 50y view struct has no idea what the data is where it comes from or how it works we're going to start off with three trivial things then build our way up from there first make a new class called view model which conforms to the observable object protocol so we can report back changes to any swiftly view watching us second i want to place this whole class inside an extension on content view so extension content view and then tab in the class like that so now we're saying this isn't just any old view model for our whole program to use it's really just for content view that's the content views view model and later on in the challenges it'll be your job to add a view model for edit view so you can see how this technique works in other kinds of views too the final small change i'd like to make is to add a new attribute to this whole class at main actor at main actor the main actor is responsible for running all user interface updates and adding that attribute to our class means we want all the class in this code anything you write in here anytime it runs anything at all unless we specifically ask for otherwise to run on the main actor and this is really important because this thing is responsible for making ui updates that's its job and those must happen ui updates must happen on the main actor in practice it's not quite so easy as you'll see in the next video we'll come to that later on now we've used the observable object protocol before with other classes but we didn't give them the at main actor attribute so how come they worked how come there was a problem there well behind the scenes when we're inside a swift ui view and we use at state object or at observed object or similar swift has silently inferred at main actor for the class we were using it understands it knows both those property wrappers mean a swift ui view is relying on some external object to trigger ui updates because that's the javascript object that's a java observable object that's their job right so it'll make sure that all ui work done through those state objects and observed objects happen on the main actor without us asking for it however that does not provide a hundred percent safety nothing does but doesn't even vaguely try and do it yes whenever you have at state object whatever swift will correctly infer main actor for that object whenever you use that inside your swift ui view it'll be done in the main actor brilliant what if you call that class from somewhere else a different class or a stroke that's not a ui view for example now the coke can run anywhere and so that's not safe because the coke could be on a background task somewhere try and publish updates to the ui and bang it'll be very very unhappy you wipe dates must happen on the main actor and so we're adding this main actor attribute to our class right up here we're taking a belt and braces approach we're saying to swift that every part of this class should run on the main actor so it's safe up at the ui more or less no matter where it's used and you'll see it's more complex in the next video but for now it's good enough if you're not sure if you're thinking oh this is hard we're not on standards at all every time you have a class that conforms to observable object add main actor just do it there's no reason not to it should always be on the main actor okay now we have this class in place we get to choose which parts of the state from our content view should be moved into the view model now some folks would say all of it get all state out of the view and into the view model other folks are more selective that's okay again there is no single definition of what mbvm looks like everyone argues disagrees that's fine i've provided you with the tools and knowledge to experiment yourself let's start with the easy stuff move all three of these at stake properties out of content view and into the view model now we don't want to use at state private var anymore because these aren't uh swift dy state values anymore they're designed to be published externally so they aren't private either they're public to be published to other views so remove at state private and instead say at published and same for locations and same for selected place get rid of at stake private and just do app published viral instead now that's going to break all sorts of code back in content view because uh we need a way to reference those values we haven't got local state anymore so instead we'll say at state object private var viewmodel is our viewmodel and that will automatically refer to the class inside our extension the local viewmodel now remember at state object will automatically know aha this means every time i call view model inside my swift ui view it will be on the main actor it'll infer that main actor thing for us but belt embraces we've said a main actor as well just to make really really sure or as sure as we can anyway now it's gonna uh still break code because we reference things like map region or locations things that were local before but now belong to our view model so fix here is to add viewmodel dot in all these places refer to viewmodel.map region view uh view model.locations lots that they place oops like that uh view model locations view model set to place a few modern locations do more locations like that hopefully that'll build our code now i missed two up here whoops there's one here and one here okay so once you've added viewmodel.everywhere there's a good chance you're wondering how has this actually helped yes my co-compiles but order has moved code from a to b how's that better yes we have just moved code from a to b but but critically as you progress in your skills and go further in your swift career you'll realize that having this functionality properties and methods and more in a separate class like this one makes it much easier to write tests for your code because testing swift ui views is complex you'll launch simulator and do virtual taps around the screen this is a class we just make one of these and just poke around it directly and see what comes out called functions are directly it's fine to do so it actually makes it much easier to write tests for having a separate view model like this now views work best when they handle presentation of data show a map show a circle show a v stack whatever but manipulation of data is a great candidate to go into your view model anywhere you want to change data now if you look to your content view code you might notice if you look carefully two places where our view does more work than it ought to where it's trying to change data and there's one here we're modifying the view model selected place and there's one down here as well and even the third one actually down here this thing so there are three places where modifying data now of those this first one is fine there's not much you can do with that really you've got a location what you want to do select the place but this is digging around in the map region then modifying the array and this is doing the same thing with your rail digging through to find stuff then modify it directly it's really not very nice uh now reading data from our view model is great you know even if it's via properties exposed with app published you know we have our map region or our locations here that's all honestly fine reading's fine but writing it writing it like down here logic this kind of code down here or down here this is not great because the whole point of excise here is to separate our logic from our layout we're going to get any logic we can out here and you can find these two places immediately if you clamp down on writing to the viewmodel data so we could say in our view model that this locations array is private set meaning you can read all you want to reading's fine but only the class itself can now write locations and so uh in this file it's just disappeared entirely thanks xcode cool xcode's angry with me let's try that again xcode there we go cool um uh in here now it'll be angry it has saying well you can't modify the array directly that's not allowed i'll understand view model dot and then view model dot you can't modify the array directly down here and down here it's not allowed you can't do that and it's giving us a big hint this kind of code this kind of logic needs to be somewhere else and so we can start by taking out this first piece of code here to make a location and put into the array command x that to your clipboard then go over to your view model and add a new method we'll say funk add location and let's paste that on in now you can remove the view model here and here and here otherwise it's good that's the method and back in our view we can just say view model dot add location which is much much nicer the second problematic code is down here where we're digging around the array trying to find a particular place then changing the array and causing a refresh um this the whole if let index block here again command x that to your clipboard and then back in the view model add another method for this we'll say funk update location location here uh and what we'll say is paste that in for a start that's fine but first we'll find out what we're actually changing what should this old place value be um so we'll say guard let selected place b selected place else return and then put on one line cause i love it on one line like that if we have that we'll put selected place here and down here will be our new location that now remove view model from here and here so code actually compiles it works much better and now uh inside our content view we'll use that method instead so we have the new location coming in here we'll do view model dot update location is new location boom at this point the view model has taken over all aspects of content view to handle data manipulation which is great the view here is to provide and present data and the view model is there to manage data now in practice a split honestly isn't always that clean in fact it's really that clean despite what you might hear online and once again that's okay once you move on to more advanced projects you'll usually find that uh one size fits all approach usually fits nobody and so we just do our best at what we have anyway in this case we now have a view model here that supports our data being handled correctly so we can upgrade this thing to handle loading and saving transparently without our main content view having to care about it let's look inside documents directly to find a particular file decode it or encode it for saving it using json code or listen to json decoder and then put it into our locations array now previously i showed you how to find an apps documents directory using file manager with a reasonable function here though we're going to package up on extension on file manager which i think makes it easier access for other kinds of projects so press command n and a new swift file called file manager dash documents directory and give it this code extension file manager static var documents directory is a url you get all the paths from filemanager.default.urls4 dot documents directory in uh user domain mask and return path zero like that and now we can make a url to a file in our documents directory wherever we want however i don't want to do that when we're both loading and saving data because it means if you want to change your saved location you've got to change both places a better idea is to modify your uh content view model here to have a static uh static a constant property here that'll store your saved location so it knows where to read and write data from in one place we can say let's save path b file manager file manager dot documents directory got appending path component saved places and i can use that when reading and writing files with the same file in both places so first things first add a new initializer that will load our data from disk we'll say init do let data is try data content of our save path locations is try json decoder dot decode array of location.self from our data if that fails oops fails then locations will be just an empty array oops like that that's loading stuff after saving previously i showed you how to save things with a string writing a string to disk but the data version using data rather than string is actually even better because let's do something quite amazing with one line of code we can ask ios to ensure the file is written with encryption so they can only be read once the user has unlocked their device this is an addition to requesting atomic rights ios does almost all the work for us so we'll say func save uh do scroll down let data equals try json encoder this time dot encode encode our locations and try that data dot write to with options the url is our save path our options will be dot atomic write and dot complete file protection give me both those things please and if that fails we'll catch print unable to save data like that honestly it's quite remarkable all it takes to ensure the file is stored with strong encryption is to add dot complete file protection to our options that's it now this approach here using json decoder and our documents directory means we can write any amount of data in any number of files someone asked a comment to a previous video can i write sounds yeah you can write sounds write movies write text files right whatever you want to just be wary of taking up too much space because users might complain anyway um that's it and it's much more flexible than uh user defaults because you know it'll load when we're ready as opposed to when the app launches straight away it's much much nicer now before with a step make a handful of small changes to our view model so it uses the new uh initializer and new save first up initialize all make sure locations is either the array of our saved data or an empty array and so up here our publish locations can now just be an uninitialized array of location it'll have a value initializer no matter what that's the first step and second we've got to call our save method after we add a new location or after updating an existing location so down here in add location we'll say save and we update down here we'll say save with that go ahead and run the app now because hopefully we should find i think i've tried before being well uh we can load and save data let's see what i've managed to do i'll go to london add a pin there and i'll call this thing uh central london then press save and we'll add one more up here somewhere approximately here approximately there uh i'm going to say this is about uh fort william i like that so two uh values they're saved immediately there's no need to delay we have a user defaults uh if i go ahead and run the app again all being well it will have saved up data and there we go central london and fort william now i know this took quite a bit of code to get the mbvm get the codable stuff working but the end result is that we have loading saving done really well because all the logic is outside our view this thing has no idea what's going on as you carry on learning in the future getting better and better at swift you'll realize it becomes so much easier to write tests for a class like this than trying to use a swifty wide view to test second we're making ios encrypt our data so that we're protecting users privacy it cannot be read or written until the ios user unlocks their device but the load and save is completely transparent as well the view has no idea what's happening here we just call init and save at the right time and it just does everything for us it's really really nice of course our app is not truly secure yet we've ensured our data is being saved with encryption but there's nothing stopping someone else from just reading data afterwards from launching the app and having a look for themselves
Info
Channel: Paul Hudson
Views: 750
Rating: undefined out of 5
Keywords: xcode, swift, swiftui, ui, ios, programming, tutorial, example, project, code, uikit
Id: kfsA87qRC3Y
Channel Id: undefined
Length: 21min 28sec (1288 seconds)
Published: Fri Dec 10 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.