Download and save images using FileManager and NSCache | Continued Learning #28

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up everyone i'm nick this channel is swiffle thinking where we cover all things swift and swift ui related and i'm super excited to share this video with you guys because we're gonna take uh what we've learned in probably the last five or ten videos in this course and put it all together for a real world example of how this could all work in a real application so what we're gonna do is download data from the internet using combine we're going to take that data and obviously put it on the screen but we're also going to practice saving that data in the file manager as well as a cache this is a great real world example because almost every app you're ever going to make involves downloading images from the internet and then saving or persisting those images somewhere so that you can reuse them again and this becomes especially important for files such as images or videos because these files are often larger and they have a lot of data so if we can just download it from the internet once and then save it locally it would be much more efficient than downloading it again and again and again and also this creates a better user experience because we already have the data the images the videos loaded into our app we don't have to get it from the internet all right guys i hope you enjoy this video we're not going to learn anything new we're going to cover what we already know and if you do not know combine if you do not know the file manager if you do not know catching i just covered those in the past couple videos i recommend going to check those out before watching this one all right let's jump into xcode and get coding welcome back everyone i am super excited about this video because we're gonna use a lot of what we learned in the last five to ten videos and put it all together and make a really cool app here we're basically gonna make a mini app in this video and i think it's awesome i think you guys will too so let's start out by actually right clicking the navigator and this time let's create a new group this is just a folder and i'm gonna call this folder downloading images why not let's be explicit with what we're doing all right and inside that folder we're going to actually create a couple more folders so let's right click on that create a new group this one will be called model models we'll right click on the folder again we'll say new group we'll call this views and then we'll do it one more time new group and it will be view models and that's right we're going to use the mvvm architecture in this little mini app we're making and we're going to actually create one more folder so let's right click it one more time create a new group and we'll call this one uh utilities now i'm adding this utilities because these are going to be classes that we make that uh are obviously helpful in our app but they don't but they're not technically part of the model view or view model section of our app so let's start this off by right clicking on the views folder and let's create a new file it's going to be a swift ui view and let's call this one downloading images boot camp go ahead and click create and click resume on the canvas i'm going to leave the navigator open quick because we are going to use it and before we get started i do want to point out that we're going to use a lot of the concepts that i've covered previously in this series and because there's so much to cover in this video i'm not gonna have time to go into each of them individually so some of the prerequisites for this video include understanding background threads understanding weak self understanding the combine framework and in that also understanding publishers and subscribers we're also going to use the file manager as well as ns catch and if you are unfamiliar with any of these i would definitely recommend going to watch those videos which i just previously covered in this course and then coming on back so if you've been following along this video will help to show you how we can put all this together and make a really cool really efficient app all right let's start this off by creating a navigation view open the brackets inside we're going to put a very simple list and we'll open the brackets again and in this list we're going to put some data but right now let's just put a text and let's just say hello let's click resume on the canvas to make sure we're all connected should say hello let's give this a navigation title and let's say downloading images all right and what we're going to download is coming from the internet and i have the website up here i'll post a link to this url below essentially it's coming from the json placeholder website which i've used a couple times throughout this course it's a free website that just has free fake apis that we can use for testing and prototyping and on this website there is a backslash photos route and this url which i will put again in the caption below and this url basically just gives us all of this data when we fetch it which is i think it's like 5000 images and each image has an album id and regular id title a url and a thumbnail url and if we look at the url here and if i copy and paste this which i have here it gives us this image for that fake photo and obviously this is not a very pretty looking image but but it is an image that i can i can see here and this is the image that we're going to be downloading into our app so what we're going to do is actually download this array of data first and this array of data obviously has a reference to the url link but it doesn't actually include the data for the image so we're going to download all this data and then separately when each of these come onto the screen we will reference that specific url for that image and then we will download that image separately and this is what we do in production apps because downloading all of this text data is pretty quick pretty fast it's not a lot of data but to download 5000 images like this at the same time would take a long time and it would be just so much extra data that we don't need so in apps we try to only download images when they're coming onto the screen so this is the data we're looking for first and i'm just going to copy this and i'm going to jump back into our code and of course we're going to need a model to download and i just realized we are also going to use the codable protocol to to decode the data from json into our custom models another thing you need to know here so let's right click the models folder let's create a new file this is going to be a swift file and and i'm picking swift file because we do not need this canvas preview that we get with the swift ui we're just gonna do a swift file here i'm gonna call this uh photo model and let's click create and in here i'm first going to create a multi-line comma with the forward slash asterisks in here i'm just going to paste in the json data that we're getting so i'm going to take this json data make it a model quick let's create a struct we'll call this photo model we will make it conform to identifiable and codable and we'll open the brackets i'm going to use the exact same values that we have here so each photo model is going to have an album id we'll say let album id and i'm capitalizing the i here and lowercase d just as it exactly is on the website this will be of type int because this is an integer we'll say let id of type int we'll say let title of type string we can see here the title is a string we'll say let url of type string and we'll say let thumbnail url of type string all right and we are conforming to identifiable because we already have an id which is super handy and we're conforming to codable again so that we can decode from json data into photo models now that we have our model let's jump back into the download images boot camp view and actually this view we need a view model for this view so because we're not using this preview right now i'm going to close it i'm going to actually right click on the view models folder create a new file this will be a swift file i'm going to call this downloading images view model go ahead and click create and what i'm going to do here is i'm going to click onto the downloading images boot camp and then i'm going to click this plus sign on the right here to add a second view to my app and then while i'm clicked over here on the right side i'm going to jump onto the downloading images view model so this way i can just see both of these files at the same time because this viewmodel is going to be for this view so in here let's create a class we'll call it downloading images view model it'll conform to observable object and we'll open the brackets while we're here let's initialize one in the view so in here we'll say at state object var let's call it vm for view model and we'll set it equal to downloading images view model all right so now our view as a reference to the view model in here of course we're going to have some data so we'll say at published var we'll say data array and it'll be of type array of photo model right that's the the type that we just made the model and we'll set it equal to a blank array to start all right let's loop on this data array in our list here so in here instead of just this text let's add let's add a for each open the parentheses we're going to use the data completion here and we're going to loop on the vm dot data array let's then press enter on this content here get rid of this nonsense and this will be looping on for each photo model so i'm just going to call it model and then in here let's just put a text and for now let's just reference the model dot title all right and now from this view model we want to actually download some data but just to get you guys a little more comfortable with how things might actually look in production apps i don't want to put the downloading data directly in this view model because this view model is specific towards this view but the downloading of this data for the photo model could be generic and reused throughout our app because if i put the downloading logic within this class it would only be able to be used for this view model on this view but if i create a custom reusable class for downloading it i could then use that downloaded data on a bunch of different views and a bunch of different view models so what we're going to do is actually right click on the utilities folder create a new file this will be a swift file and i'm going to call this photo model data service click create and in here we're going to create a class of course will be called photo model data service and we'll open the brackets this will be a singleton so we'll type static let instance equals photo model data service and this is our singleton so this will be the only instance of this class in our entire app and if we leave it like this again for those of you who are just joining uh there's nothing actually stopping us from adding another instance in our code so for example in here if i said let new instance i set it equal to a new instance there's nothing stopping us from doing this but we want to avoid this so we will create a private init open close parenthesis and then open the brackets and because it's private it will we will only be allowed to initialize one within this class so if i tried to build my app right now this is going to come as an error message see it's inaccessible so perfect and we're going to do is we're going to reference this instance in our view model so in here we'll say we'll say let data service and we'll set it equal to the photo model data service dot instance alright so this instance is this instance and then in this photo model data service let's add some logic to actually download this data down here let's create a func let's say download data open close parenthesis open the brackets the first thing we need is a url we'll say guard let url equals url open the parentheses we'll use the string and i'm just going to copy the url from this website here which again i'll put in the comments below and just paste it right in here and we'll say else and if we can't get it we'll just return but we know we can we know this is a valid url i'll move this over a second because we're not using this right side and by the way i'll mention if this website ever goes down i'll post a replacement link in the comments below as well all right i'm going to move fast here because i covered this extensively a couple videos back we're going to download this with combine so we're going to use url session look for just the regular url session here dot shared dot data task we're going to look for the data task publisher for url of course we're going to pass in our url here we're going to take this we're going to subscribe on and we're going to subscribe on the dispatch queue dot global and we'll use the dot background quality of service we're going to receive on the dispatch queue dot main we usually want to receive on the main thread we then need to handle our data so we'll call dot try map and we're going to try to take the data and make sure that it is good data now there's a little bit of logic here so i'm going to make it so i'm going to make this its own function and down here we'll make a private func and we're making it private because the only time we need to access it is from within this class so private is perfect here and we'll call this handle output open close parentheses open the brackets and this function is actually going to take a parameter called output and will be of type which we can see right here urlsession.datataskpublisher.output urlsession.datataskpublisher.output and we can see in this trimap that it throws and it returns some type and this is t because we can tell it what type to return so here we'll add throws and it will return data all right i'm going to get rid of this try map for a second just so we can work down here first we want to check the response in this output so we're going to say guard let response equals output dot response and we can see here that is a url response but we want to make sure it's an http url response so we will cast it as an http url response then we want to make sure that this response dot status code is greater than or equal to 200 and the response dot status code is less than 300 and we'll say else so if this fails and it's not a http url response or the code is outside of the 200 range we're going to throw an error so here we'll say throw and we'll throw a url error open the parentheses and i think in here we can call dot bad server response all right and then if it's successful down here we will return the output dot data all right let's clean that up and again i'm not covering this in detail because i covered this a couple videos back in the download with combine and also in the download with escaping video so if you're confused here just go jump into one of those videos you'll see exactly what we're doing i explained it in detail up here let's try to map it and here we can call handle output and we can actually even delete this parameter here and we try to map it we handle output and if we get good data here we can then decode it so we'll add decode and if i look at my website this is going to return us an array of photo models so we're going to decode to an array of photo model dot self and the data we know is json so we're going to call a json decoder all right then if we get this all right photo models of course we want to put it somewhere so we'll call sync and first we have the completion so let's press enter on the completion this will just call completion and we're going to switch on the completion here we'll say we'll add the completion we'll open the brackets and there's two types so if we say case and press the period we can see the completion will either be finished or failure so for finished we'll add a colon here and we're actually not going to do anything so we'll just break then we'll do case dot failure and if we fail here we're going to get an error and that's because and that's because as we saw in our handle output it throws an error so if this failed it will throw this error and that error would then come through right here so we'll say let error call in and then down here we're not gonna really do anything because i know this is not gonna fail of course but right now let's just print uh error downloading data and then we'll backslash open close parentheses and print out the error just in case if for some reason like this website goes down this will throw us an error all right and then of course if we receive this returned photo models we want to put it somewhere so this will be returned photo models and we need to store these somewhere so up here let's put it underneath the instance but above the init will create an at published var we'll call this photo models it'll be of type array of photo model we'll set it equal to a blank array and when we receive this value we want to set models equal to the return photo models now of course we know this creates a strong reference we want to make it weak so we'll do week self here and this will become optional last thing we need to do because we're getting this warning message here is store this cancelable publisher somewhere so we'll call dot store and we have and we need a set of any cancelable so at the top here we will import combine underneath this let's add a var we'll say cancelables and we'll set it equal to a set of type any cancelable and we will open close parenthesis to initialize it i'll just take this cancelables and store it here all right so if all goes well this will download our data and put it into this photo models array let's call download data on the init so when this gets initialized which will be as soon as our app runs it should download this data and now the only problem we have although we have a reference to this instance we don't have any way to get the data from our photo models are right here to our data are right here because this data array is what's going on the screen this is this photo models is just storing what we downloaded it's not going to be actually seen on a screen so in our view model we're going to create an init open close parenthesis open the brackets we're going to say funk add subscribers open close parenthesis open the brackets now i showed you guys a couple videos back how we can add custom subscribers in combine and what we're going to do is subscribe to this published array here so anytime something gets added to this array it will because it is at publish it's going to publish the results and because it's publishing we can subscribe to those published values so we'll say so in here we'll reference the data service and then we'll call dot photo model so we're going to look for the one with the money sign because that is the publisher all right and then just like we did down here where we sync and we take what gets published and put it into this photo model here we're going to take what gets published and put it into this data array so we'll say dot sync and we don't need to deal with the receive completion here we don't really care about the errors so i'm just going to use the second one with the receive value click enter and this miss here will be returned photo models again let's make it weak self and then in here we will set self dot data array equal to returned photo models and of course we need to store this somewhere just like we stored this cancelables so we're going to import combine we're going to create an uh var we'll say cancelables we'll set it equal to a set of any cancelable and we'll open close parentheses and then we here we will just store it in cancelables now i could have actually referenced the data service and put it into the same cancelables bucket but it's probably good practice to separate these so that if we want to cancel all the subscribers in our view model we can do that separately from the data photo data service here all right a lot of code but this should now be working we need to just add the add subscriber in our knit so when we run our view now it should have a reference to the view model which has a blank data array but this is adding a subscriber to our data service we can see the subscriber here and in our data service which is here it should be it should download the data on the init so once this data is downloaded it should put it in here into the photo models that is being subscribed to down here which will take that data put it into our data array and this data array should be on our screen here the vm.data array so i'm going to x out of this right side here i'm going to bring back our canvas and if we click resume on the preview hopefully if we did everything right we should see a little bit of data on the screen we gotta press run so that it actually starts to download and look at that so these should be all the titles for the photos and if you are confused at this point this is a perfect time to stop and review what we've done because it's it actually was pretty complex we made a view we made a view model we subscribe the view model to a data service and this is a really good architecture that you guys should get comfortable with when using swift ui and combine all right now of course we don't want this just plain text right here we want to actually get maybe an image and some other data on the screen make it look a little better so in here let's just mess around with this real quick and then we're going to make it its own view so let's add an h stack open the parentheses let's start with a circle let's give it a frame with a width of 75 a height of 75 we don't need the alignment let's add a v stack to the right of it and in the v stack let's add a text with the model dot title and another text with the model dot url let's make the url.foreground.gray and maybe it's called italic let's make the title uh let's give it a font of headline let's take the v stack let's add alignment of leading and let's also give it a dot frame with a max width of infinity and let's add an alignment on the frame to leading all right so text should be lined up here obviously we don't have the the images downloaded yet so we just have this placeholder circle all right if i scroll down this list it looks like we are getting a ton of data which is awesome i'm going to take this h stack and make it its own view so i'm going to copy this i'm going to right click on the views create a new file it's going to be a swift ui view and let's call this downloading images row click create click resume in here i'm just going to paste that data and of course we need a reference to this model so up here we'll say let model and i'll be of type photo model should get rid of our error our errors and down here if i click fix and we need to pass in a photo model here so i'm just going to type in photo model open the parentheses and let's just create a fake one here let's give an album idea of one in id of one title let's just say title url we'll say url here thumbnail url here click try again hopefully our one pulls through beautiful and obviously it's not a full screen so when it's not a full screen in the preview i like to add a dot preview layout dot size that fits just so we can see exactly what we're looking at here let's add a little padding around it too just to make it look a little better on our preview all right nothing fancy and now of course we don't want a circle we want to actually download an image so that is going to be its own view itself as well so i'm going to right click on the views create a new file this again will be a swift ui view and let's call this downloading image view go ahead and click create and this is going to be just that one circle where the image is let's resume to make sure we're still connected so in here let's add a z stack open the brackets and for a second let's add at the top here at state var is loading of type bool and we'll set it equal to true just for a second and then in our view we're going to say if is loading open the bracket so if it's loading we're going to add a progress view then we'll say else so if it's not loading here we're going to add our image so we're just going to put a circle for now because we don't actually have that image if i click resume we should just get that loading indicator let's make this again dot preview layout dot size that fits and we actually know that in the row this is going to have a frame of 75 and a height of 75. so in our preview let's also just add that frame beautiful and now there's a lot of logic that we want to add to download this image and then put it onto the view so i could add all that logic directly in this view here but we're using the mvvm approach so what i can actually do is create a view model just for this view so in the same way that we have a view model for our entire screen we're gonna have a view model just for this small part of the view just for the image so what i'm going to do is right click on the view models create a new file this is going to be a swift file i'm going to call this image loading view model go ahead and click create i don't know if this is the best title for it but i just wanted to be explicit in that it's different from the downloading images view model so in here so what i'm going to do again is i'm going to open up the downloading image view that we were just on and then i'm going to click the plus on the right here so let's get rid of the canvases and while i'm clicked on the right side i'm going to open up our image loading view model so in here let's create a class it'll of course be called the image loading view model we conform to observable object and open the brackets in here we're going to have an at published var we'll say image of type ui image and we don't have a reference to the ui image here because we have not import swift ui like we normally do so we will import a swift ui this will be a ui image and we'll make it optional in case we don't have it yet we'll set it equal to nil to start we'll also add an at published var we'll say is loading this will be of type pool we'll set it equal to false as a default and while we're here let's initialize this in the view so we're going to create an at state object var i'm going to call this uh loader and of course we're set equal to a new image loading view model so now we don't need this as loading here because we're going to use the one that's in the view model so this will instead be loader dot is loading right so this is referencing this published variable here now so if we're not loading let's then check if we have this image so we'll say else if let image equals loader dot image and then of course in here we will instead add an image with the ui image and we will pass in that image right here this image let's make it resizable so that it changes to fit our size and then let's also make it dot clip shape of a circle so the square image looks like a circle on our screen all right and now of course we need some logic to download an image here so what i'm going to do is create a func let's call it download image let's open close parentheses open the brackets so first we're going to need a url and we don't have one yet we'll say guard let url equals url we use the string and let's just make it a blank string for a second we'll say else return so if we get that url we can call uh url session dot shared dot datatask publisher with the url and in here i'm going to dot map it this is mapping the result so in here i'm going to click the second one let's click enter on it and i'm going to map here and before we did try map because i cared about the errors but here i don't really care about the errors so we're just gonna straight up map it and this of course is gonna give us data and a response so we'll say data comma response and we want to return a ui image and in here we can just return and we can add a ui image from the data and of course this will just be the data this could actually be optional we can make this a little bit of code even more efficient by instead just calling so up here i'm going to actually call the map open the brackets here and i can add a ui image open the parentheses from data and the data will be money sign 0 dot data so this one line of code is doing the same thing as this line of code this is just writing it out a little bit more so now that we have the image let's make sure that we are not receiving on the main queue called dispatch queue dot main and once we get the image let's dot sync it somewhere so on the completion i'm going to press enter let's move this over real quick uh we're not going to actually use the completion so i'm just going to add an underscore there and let's just get rid of this code quickly then if we receive a value this will be the returned image let's make it weak self and then of course in here we want to set self dot image equal to the returned image and we're going to do is when we call download image we are going to set is loading equal to true and then every time we return out of this function we'll set is loading back equal to false so if we fail to get this url we'll say is loading equals false let's break this up here and if we complete this publisher down here we would also say self. is loading equals false and again this is a strong reference so here we can add so here we will add weak self again all right let's get rid of some of this extra code and let's call this download image when we initialize we'll init and we're going to call download image now we have one problem of course and it's that we don't have a url for this image and the last time we made a url in our photo data service uh we know every time we're downloading data it's from this url so we could paste this hard code it right here and it was working perfect but every time we have an imageviewmodel it's going to have a different url right because the url in this image is different from the one on this image it's different from the one on this image so we actually need to pass this url in directly into this view model so we're going to create another variable we'll say let url string of type string and then we're going to add one of these when we initialize this so here we'll pass in the url of type string and here we'll set the url string equal to our url and then down here i will reference the url string all right so now we have the image loading view model and we're gonna and if i try to run it we're gonna get an error because when we go to initialize the image loading view model we need to pass in a url so coming so i'm going to close the view model quickly it's an x out of here and now this image view doesn't have any reference to that model yet and we need a url being passed into this image view so what we can do is create an init open close parenthesis and open the brackets and when we initialize this image view we can pass in a url of type string and now if you've never done this before you might think oh we can just fix this here and we can pass in the url from this init into this view model but unfortunately we can't do that because this is going to be getting initialized at the same time as our init so what we have to do is actually initialize this loader not directly right here but from within the init so what we're going to actually do is make this loader of type image loading view model let's get rid of the code to initialize it we're going to initialize it from in here and to initialize a state object because we actually need this property wrapper we use the underscore and we'll call underscore loader that's this that's this right here set it equal to a state object open the parentheses and then we have a wrapped value and this is basically the type that we want to put in the state object so we have the state object and then we have the type so here we're going to put a image loading view model open the parenthesis and here we can pass in our url so we'll take it from here pass it directly in here and now in our preview we're also going to have this error so what i'm going to do is jump into this album id here i'm going to copy the first url for the first photo here and let's just paste that as the url string for our preview so if i open the canvas and i click resume hopefully when we load this one image we can actually download that one image and we'll see it on the screen and if i look back this image actually looks like this it should be green it's a green box it says 600x600 not a fancy image but it is an image and we are downloading it from the internet so let's click the play button here and it actually looks like it is not working so let's jump into that loader because obviously it is loading here so let's jump into the image loading view model and we can see already that we have the warning sign because we didn't store this anywhere so at the top let's import combine sorry guys let's add our cancelables so we'll say uh var cancelables we'll set it equal to a set of any cancelable open close parenthesis let's just store it in that cancelable set hopefully the error goes the warning should go away now and let me jump back one more time hopefully that was the issue and that was hopefully that was the only issue we had so let's resume this canvas one more time beautiful so i have my image pulling through and i hope you guys do too at this point i know it doesn't look like an image it just looks like a color but i can promise you this is an image that is being downloaded from the internet all right now that we have this working we're going to put our download image view into each of our rows so i'm going to jump to the downloading images row the first thing i notice is that this photo model doesn't have the actual url which we're going to need so let's copy that url and i'm just going to paste it where it says url here in our preview i'm going to put it for the thumbnail as well and let's take this circle let's replace it with a downloading images image view and we need to pass in the url and the url of course is going to be the model dot url so if i click play here it should have our image beautiful and if i go back to our downloading images boot camp now we can take out this h stack and instead add the downloading images row and every time we created downloading images row we need to pass in our model so i'm going to take this model pass it right in here and if we did everything right we should now see a whole bunch of images beautiful so i know it was a lot of work to get to this point but we are downloading this data from the internet and then we are downloading all these images from the internet when they come onto the screen and this looks awesome all right and now is for the fun part and the whole reason kind of making this video so we can learn about catching and saving images because right now we're just downloading these images so let's take this downloading images bootcamp let's run it on a simulator so i'm going to go up to the app.swift file let's make this the first file in our app and i'm going to go back and when we go to actually download each of these images which is being done in our image loading view model when we call download image here i'm just going to print out downloading image now all right going back to the view let's actually run the simulator quick all right so when our screen loads we can get a print out here we can ignore some of this top swift ui nonsense that's coming through and we can see a bunch of downloading image now printouts so what's happening is we're first downloading the data for all of our images and if i start scrolling a little bit on this simulator we can see from this little indicator here that this is a very long list of data it is much more than this 10 images however we're only downloading images for the images that are on the screen so the download image now is only being called for these images that we see here all the ones down on the list are not being downloaded yet right so the ones down on the list we have the data but we don't have the image and as we start scrolling down we can get all of our downloading image calls because as these come on to the screen they're getting initialized and then we're downloading and it's pretty quick to download but if i roll scroll really fast we can start to see some loading indicators here as they download now these are relatively small images so they download really fast and that's great for our app and we can see all the downloading images for each one so this is good but if i press enter a bunch of times here just to see where we stop if i start scrolling back up on this view right so anything above where i'm at right now on the on the screen we should already have downloaded all of the ones at the top were downloaded as we scroll down to this point so if i start scrolling back up unfortunately we're still going to get these download images again so what's really happening is i'm now downloading all of these images a second time and you can imagine how this is not very efficient because if i have a user that's scrolling around my app and they're going forward and going back every time they come back to the screen we're going to re-download this image so what we're going to do to make this more efficient is basically save these images using the file manager and the catch so we're going to do is first use an ns cache so let's right click the utilities create a new file and this will be a swift file and we're going to call this photo model catch manager click create and in here we're going to create a very basic cache we're going to create a class we'll call this photo model catch manager open the brackets it'll be a singleton because we're going to need one for our entire app so we'll call static let instance equals photo model catch manager we're going to make it a private init open close parenthesis open the brackets close the brackets and then let's create our catch we're going to say var photo catch of type ns catch and when we do this we get that handy error message to tell it what type of objects we want to put inside and the keys are going to be ns strings and the values will be ui images we don't have that and we don't have that yet because we have not import swift ui the key will be a ui image and we'll set this equal to and we'll open the brackets and now we just need to return a photo cache before we do let's actually open close the parentheses onto this brackets to initialize it and we'll say var catch and we'll set it equal to and i'm going to just initialize a new one of these right here i'm going to then we're going to return the catch of course and before we return it we are going to call catch dot count limit and this is the maximum number of objects the catch can hold so when we are downloading 5 000 images here we can tell the catch to maybe only hold the most recent 100 images so we'll say catch dot count limit and let's set it equal to maybe 200 images we'll also say catch dot uh dot total cost limit and this is the total amount of data that we can put in the catch and i'm going to set this equal to 10 1024 times 1024 times 200 which i think is 200 megabytes but i'm not positive there you guys can create your own custom limits when you create when you create your app of course we're going to create a function to func add and when we add we need a key and then a value so let's add a key of type string and a value of type uiimage and we'll open the brackets and in here we're just going to say photocatch dot set object and the object of course is going to be this value and the key will be the key as ns string we're also going to create a func to get it from the catch and this of course we're going to need the key of type string and it's going to return us a ui image and it'll be optional in case we can't get it and here we can just return photocatch dot object four key we're passing the key as ns string all right we're not going to deal with removing from the catch manually for this video so i'm just going to use the add and the get functions and i'm gonna make this full screen quick and let's actually right click the utilities create one more new file would be a swift file and let's call this one photo model file manager go ahead and click create and what i'm going to do is open the cache manager and then i'm going to press this plus over here on the right and while i'm clicked on this right side i'm going to open up the file manager because these two classes are going to be essentially the same thing and they're going to do essentially the same thing except one is using a catch one is storing it in the file manager so let's import swift ui let's create a class let's call it photo model file manager open the brackets let's static let instance equals photo model file manager make it private init open close parenthesis open the brackets and we're going to create a folder to save all of our photos in so let's create a funk create folder if needed open close parenthesis open the brackets let's actually make it private because we're only going to call it from within this class i'm going to call it from our init so create folder if needed goes in here and the first thing we need to do is get a path to that folder so let's make a reusable function we'll say private func get folder path open close parenthesis and then open the bracket and this needs to return a url we'll make it optional in case we can't get the folder path in here we're going to return the file manager dot defaults dot urls we're going to use the foreign in for the caches directory this is the caches file manager directory it's not the same as this photo cache and then in dot user domain mask we'll call dot first which is optional and then we'll call that appending a path component and this will be the name of our folder which we're going to reuse so at the top here i'm going to create a constant we're going to say let folder name set it equal to uh downloaded downloaded underscore photos you can make this whatever you want just don't add a space in here so underscores work perfect let's take that and make that the folder name here so that's our folder path and in create folder if needed first let's get this path so we'll say guard let url equals get folder path else return return all right and then let's check if it doesn't exist because if it doesn't exist we'll need to create it so we'll say if file manager dot default dot exists file exists at path and we're going to pass in the url dot path and we're going to say if it does not exist so we'll use the exclamation point for if it doesn't exist open the brackets let's actually make it a little bit bigger while we're working in here quick in here we're going to add a do catch statement and we're going to try to call file manager dot default dot create directory at and the path of course is our url with intermediate dictionaries is almost always true and then we don't have any attributes so this will be nil if we create this let's just print out created folder folder then we'll say cache let error and here we will just print out error creating folder then backslash open close parenthesis and print out that error we shouldn't get this error but we are being safe coders so we have our create folder if needed and now that we have the folder we want to be able to just like in the cat we want to be able to add and get i'm going to copy this add function and paste it here except of course the code is going to be a little bit different and then in order to add a function we need we need we actually need a path where we're going to save it so not the folder but the path for the actual image so i'm going to create one more private func get image path we'll pass in the key of type string and this will return us a optional url as well and here we're first going to say guard let folder equals the get folder path else and if we can't get the folder passed we will have to return nil and if we can get it we're then going to return the folder dot appending path component the path component is going to be the key plus and then here i will add dot png so just to write that out quickly the folder path is going to be something like dot dot backslash downloaded photos that will be the folder then the image path is going to be something like downloaded photos backslash and then here would be the image name dot png so this is what we're doing in this function here down in this ad we're going to first check that we can get the data from this image we're going to say guard let data equals value dot png data i think it's a png image we might have to change that in a second we're then going to say let url equals get image path and for the key and we'll say else return we're then going to add a do catch statement and we're going to try to save it so we will say so we'll say data dot right two we'll pass in our url then we'll just catch the errors we'll say let error we will print out error saving to file manager backslash open close parentheses pass in the error we shouldn't get that error but again we are being safe coders and we want to create one more function so i'm going to copy this get i'm going to paste it here we're just going to change up this get quickly and in here we're going to first say guard let we'll say url equals get image path for key so we're making sure we get the image path and then we want to make sure that it does exist so we'll say file manager dot default dot exists file exists at path and this will be the url dot path and we'll say else if we can't get the url or it does not exist we will return nil and if it is successful we will return a ui image with the contents of file and we'll use the url.path so now we can add and basically save to the file manager and then we can get it back from the file manager all right we now have our two classes we have our file manager and our cache manager set up i'm going to open up the navigator x out of this right screen here and let's jump back into our uh image loading view model and here's where the magic is gonna come so in our image loading view model let's now reference one of those two managers let's first start with the catch manager so in here let's add it maybe right between these two let's say let manager and we'll set it equal to the photo model catch manager dot instance that was our singleton class and what we're going to do is actually create another function we'll call funk get image open those parentheses open the brackets first thing i want to do is try to get the image from the cache so we'll say manager.get and you'll notice here that we need a key for this image and we do not have a key in this class yet we only have a url so we could use the url as the key that would definitely work but because these models also have ids because the photo model also has an id i'm going to use the actual id instead so in this init let's also add a key of type string let's add a constant here let image key of type string in here we'll set image key equal to the key now if i try to run it we should have a couple errors because we need to add this key when we initialize this so let's jump to where those errors are in our loader here let's click fix i love how xcode gives us all these all these issues and of course we need to pass in a key so in this init let's also add a key of type string and the key will of course be the key try to run it one more time it's going to give us one more error now it's going to give us another error now so the key down here let's just add a fake key of 1 why not it should be a string and then we have one more error and of course it is back when we are initializing the image view and we could pass in our key and our key is going to be the model dot id and the id is actually an integer so we are going to make it a string by using the backslash open close parenthesis and putting the int inside now i do want to point out here that i could have just taken this model this photo model and passed it directly into the image view so instead of taking out the url and the key we could have passed this model all the way down through into the image view model and then here we could have passed in in the model and then just used from here we could have gotten the key from the model that would work totally fine but it's not very reusable because by doing it this way i could now use this same download image view for anything in my app that has a url and a key so if i have photo models but if i have also maybe user models i could also use this download image view and use a url for maybe a user and a key for the user's id so the reason i'm doing this is just to show you guys how we can make something a little bit more reusable rather than specific to this screen all right jumping back into the image loading view model we now have our key which is right here and when we call manager.get we can see that it optionally returns an image so it's going to try to get it from the catch so we'll say if let saved image equals manager dot get and the key of course is the image key open the brackets if we get this saved image let's just set the image equal to saved image and here let's print out getting saved image we'll say else so if we don't have the image already in the manager if it's not already in the cache we will then call download image so let's take this print statement and just explicitly put it right here and then instead of calling download image when this initializes we're going to call get image so we'll try to get it from the cache if it fails we'll then download it and the last thing we need to do is when we do download an image after we're done downloading an image down here when we have this returned image we then want to actually save it in the cache so we'll say self dot manager dot add all right and because we're adding this self multiple times let's actually check that we do have self here let's make it a strong reference so here we'll say guard let self equals self let's also check that we do have an image because if we don't have an image we don't want to be adding nothing to the catch so we'll say let image equals returned image then we'll say else return all right so we're checking that we do have a reference to self that is still valid and that we do have a returned image if we have this image let's put it here we can then take out the optionals we can then add the self dot image key and finally the value will be our image so when we're done downloading it should add it to the catch so that if we go to get it again we would then get it from the catch let's press play on the simulator quick all right so if i run it now on the simulator we should get to see some magic because right now we get these printouts we downloaded all these images and as i scroll down it'll keep telling us that we're downloading all of these images which is exactly what's expected but this time if i scroll back up because we're using that catch manager i scroll back up we're actually not going to download all these images a second time instead they're going to come from that catch which is so much more efficient in our app because now we never have to download the same image twice and if i'm a user and i'm scrolling around here on the app which users will scroll around all the time we are now not downloading anything on the app right now anything on this screen which is awesome if i scroll down to the bottom to new images of course we're going to start downloading but those will all be added to the catch as well so this is so much more efficient than where we started because we're not going to download the same image twice this is also more efficient because it's so much faster because instead of waiting for it to download and then load into the app we already have it in the app we can just put it on the screen so we don't see any loading indicators when we're pulling from the catch here which is awesome now one thing i want to note here is that when we're using a catch it is saving it in the memory which is basically saving it in the live app right now so if i open up this little spray bottle on the left side of xcode and click on the memory we can actually see that the memory is building up here so it's at 230 megabytes and if i scroll down more i think it will even go higher up to like 300 or something and you'll remember when we set our catch i'm going to go to it when we set up our catch manager we set the total cost limit to i think 200 megabytes so i would assume about 200 of this 300 are in that catch and one of the cool things about the catch is that if it ever gets too high it will automatically start emptying some of the old data so i think i have a ton of rows here and if i keep scrolling eventually it will break that limit and we should see the cache start to empty out a little bit which is really cool because we don't actually have to add any code to empty it out it will automatically do it and that's why we use an ns cache we can see here that it came down a little bit so it probably emptied out some of the old images and then it started adding up new images again as well so this is super efficient and we're very thankful as developers that we don't have to actually manage that process now this catch is relatively small we can see the memory use is pretty low over here we're not in the red or anything so i wouldn't be worried about this at all in my app but you could imagine a situation when you have when the catch and the memory is starting to build up and up and up and if it ever gets too high it will start to slow down your app and you could even start getting crashes or things like that so the catch definitely should have a limit and when you're using this you should definitely test around play around with your app after you have it made and just make sure that this is not going crazy the memory is not going up and up and up forever so we can see here that like my memory is staying around like 330. it's not going into like the thousands right it's staying relatively around here which is great and now one other thing i want to mention here is that the catch does not save permanently right this is a temporary catch so if i close my app and run it one more time we're going to get printouts again that we're downloading the images now because they were temporarily saved for that session but they weren't safe permanently so if i close the app and reopen it i now have to download these images again so of course we're downloading images and then again they're back in our temporary catch so this is very efficient for images that are like important to the current session but maybe not important to this user forever so perfect example is like on instagram if i go on instagram and i go to some random person's profile and i scroll down through their posts those posts i'm using in this session and i could probably catch them but those posts aren't important enough to me to actually save those on my device right because i don't need that every time i log into instagram i only need them on this session when i'm on that person's profile so now you guys know how to use the cache and you can see how it is very beneficial and now let's actually just take out the catch manager and instead let's use the photo model file manager dot instance and when we set these up we set them up with the exact same functions so we have the exact same get function and the exact save add function so it should work without changing any code in our view model and if i run this now now that we're using the file manager we are again downloading all these images and if i scroll back up we are again getting the saved images but this time we're saving them to the device and to the file manager and we're getting them back from the file manager they're not being saved in the memory so the beauty of this is that if i look at the memory usage here this is at 48 millibates megabytes and if i start scrolling down we can see maybe it spikes to 50 but before we were at like 300 or almost 400 at one point so we can see how the catch was using so much more memory than the file manager so obviously this file manager is more efficient and is probably better but we don't want to save not important data to the file manager we only want to save stuff that is going to be important to the user over and over again so that we can save it to the device forever so if i had something like this with like 5000 rows i would not use the file manager here but if i had something like the user's current profile picture or maybe like images on the main screen of my app that every time you open the app you'll see that image i would put that in the file manager so again the file manager is also saving to the device so if i close this app and run it one more time when i load it we're actually immediately getting these saved images and that's because these images are now saved to the device they weren't just temporarily saved in the memory so file manager and catch are both equally important but they should be used for different things i would say more often than not the catch is better for general images on your app but you should definitely use the file manager for things that are going to be recurring and super important to the user now you guys are an expert at both and i hope you enjoyed this video because it was a ton of code and i know it was not easy if you are following this course i would highly recommend going back through the code that we just set up and getting very comfortable with the structure of the files that we just made because this is a very real world architecture that you're going to see and should use in your apps we have our views which are the actual ui we have our view models which are which are managing the data behind some of the views we have our model which is the data that we're downloading from the internet then we also added these utility classes which are in our case singletons and they're doing some additional logic in our app such as managing the cache managing the file manager managing the download data and again we could have put this download data right into the view model but i wanted to show you this setup as another variation that you could use in your app because if i wanted to create another screen now that also referenced this same data here i could just create another totally separate view from this view and when i create a view model for that new view all we would have to do is reference uh this instance and it would have access to the same photo models array here so we didn't need to do that but it was good practice and i hope you guys enjoyed this video so thank you guys for watching and if you did feel like you learned something or you enjoyed this video please don't forget to like and hit that subscribe button and as always i'm nick this is swiffle thinking and i'll see you in the next video bye
Info
Channel: Swiftful Thinking
Views: 2,107
Rating: undefined out of 5
Keywords: SwiftUI download, SwiftUI download images, SwiftUI save images, SwiftUI save to cache, Swift save to cache, SwiftUI save to FileManager, Swift save to FileManager, SwiftUI download combine, SwiftUI how to use combine, SwiftUI how to download with Combine, SwiftUI combine download, SwiftUI download and save images, Combine SwiftUI, FileManager SwiftUI, NSCache SwiftUI, SwiftUI memory, SwiftUI memory leaks, SwiftUI MVVM, MVVM SwiftUI, SwiftUI MVVM Combine, MVVM Combine
Id: fmVuOu8XOvQ
Channel Id: undefined
Length: 69min 52sec (4192 seconds)
Published: Sat May 01 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.