I am working on an app where I need to download lots of images locally (so that they are available offline). The number of images can be 100 - 10,000. Each image may vary from 100K- 250K
I can do this via a NSOperationQueue and I have the code to make this work already but this question is more of a conceptual nature. I am not sure what is the best approach to take here.
1) Do I download all images as soon as the user logs in for the first time ? Based on the number of images, this could take a long time and what if the user closes the app meanwhile. I understand there is a limit on the time that can be spent by a background process in this case? Honestly, I dont want to do anything in the background (ie when app is closed)
2) Do I download images when a particular category is selected by the user? If a category has 800 images, then what happens if the user selects another category before all of those 800 images are finished downloading? I can always start threaded downloaded but will the thread keep on running if the user selects another category ?
3) Put something in "Settings" to let the user decide this themselves. Something like "Total Images: 8000" Images Available : 2000 and a button to say "Download All" which would display a UIProgressView of what's going on....so the user will probably wait till it's all done.
Or some other approach?
Thoughts?
As far as I understand from your description, including comments, I think that the best approach would be downloading all of your images in a thread and make the download process resumable. In this way, you are going to mirror a remote database of images for offline use.
What this entails is:
you keep track of which images you have downloaded;
each time the app starts/resumes, you start the downloading thread exactly from where you left;
you should also provide a mechanism so that the user is suitably informed when he is trying to access an image which has not been downloaded yet, but I think this should be no problem (you might also provide a progress indicator somewhere).
I would only download the image when the user actually needs to do something with the image. Your users will hate you if you download all the images upfront. :)
Related
I'm developing a chat app with Firebase. Am currently still in development phase.
Profile pictures of test users are uploaded to Firebase Storage, and are downloaded in the home screen (with all the pictures). I realized that with that I very quickly used up storage download requests (easily hit 3,000 requests in one night, and hit the free plan quota!).
What are some best practices I could use to minimize download requests? Just to be sure I'm doing it right - I'm sending a GET request to the Firebase Storage url directly: https://firebasestorage.googleapis.com/... to download the image. Is that the right way to do it?
Two suggestions that might help:
Cache your images! If you keep requesting the same images over and over again over the network, that's going to use up your quota pretty fast. Not to mention your user's battery and network traffic. After you retrieve an image from the network, save it locally, and then the next time you need an image, look for it locally before you make another network request. Or consider using a library like PINRemoteImage that does most of the work for you. (Both on the retrieving as well as the caching side)
Consider uploading smaller versions of your image if you think you might be using them often. If your chat app, for instance, saves profile pictures as 1024x768 images, but then spend most of its time showing them as 66x50 thumbnails, you're probably downloading a lot of data you don't need. Consider saving both the original image and a thumbnail, and then grabbing the larger one only if you need it.
Hope that helps...
My app displays images in tableView cells What I want to achieve is to load the image in sequence even if the cell has disappeared off the screen. For example, I have cell 1 to 100, the cell is displayed and the images starts to download in background from 1 to 100 even if the user have already scrolled to cell 78. I'd also want to only download one to two image at a time so I don't bog up the network. With these criteria in mind, I was wondering what sort of setup would be most appropriate?
At the moment, I am using AlamoFire to download image. Would a combination of Alamofire with some sort of NSOperationQueue be suitable. I am very un familiar with NSOperationQueue at this stage. So I thought I'd try to find a recommended industry practice before I start going deep into setting NSOperationQueue
I'd also like to combine the functionality to continue the download even when the app is in background
Note. Any alternative library or framework solution is also welcome
The best solution out there is SDWebImage. The link to this repository is here.
If you wanna do it your own way without having any dependency then you have to follow the same things that SDWebImage does.
In short,
Asynchronously start download in thread, which downloads the images from the URL concurrently.
Then after downloading, use NSCache, to store the image and use the
imageURL as the key.
After storing the image in NSCache, also write the image in NSCacheDirectory, with the imagename same as that of its URL.
Now while fetching you have to check, whether the image exists in NSCache, using the imageURL as the key you can easily search that. If found, return image or then search NSCacheDirectory, whether the image exists there or not. If not then you must download the image and follow the steps once again.
Now NSCache is like the RAM. The data stored there will remain there as long as the Application is active. Once terminated, the data in NSCache will get cleaned.
Hence we also write the file in NSCacheDirectory, as data over here is persistent to as long as the app is not deleted from the device.
I hope I could make you understand as to how this thing works.
Cheers.
I have an app which communicates with a server. In this app I have a tableview in which I display several people from my company (their first and last name and their profile image).
Every time the tableview opens or needs to refresh, I fetch the user list from my server. These users will all have an image_name, which I try to look up in an array on the app itself. If I can't find it there, I load it from the documents dir, if I can't find it there either I download it from my server and save in locally on the device to prevent future downloads.
This works very well and it's a very easy way to manage the users and their images, it also makes sure that I download an image only once if several users have the same image (e.g. the company logo when they haven't uploaded an image yet).
The problem is that I don't keep a reference to these users so the app has no clue which user uses which image OR even if an image is still in use.
So when person A has image X it will be downloaded to the iPhone. If user A then changes his image to Y, the app will download and display image Y correctly. However, image X will never get deleted from the persistent data.
I ask you, the stackoverflow community, what's the best way to handle this?
Should I start keeping a reference to my users so I can also keep a reference to the old image?
Is there any way to find the timestamp of the last time and image was read from the documents dir?
Should I store the image names in coreData and all the references to them? (some kind of custom ARC logic)
...
At some point in time you have the list of used images, at this point in time you also have a list of images saved to disk. Once per day you can take this information and, on a background thread, do a comparison of the used and saved and delete them. This shouldn't require any additional data storage.
If you wanted to allow images to hang around for a while after they stop being used you can 'touch' the file (update the fileModificationDate) each time you use the file and then later you can check the modification dates of all images and delete on that basis.
You could add a prefix to the image that you download and when you fetch images, check all images in persistent storage for this prefix and then remove if there are any. You should only need to delete (maximum) one image every time your client fetches, which wouldn't be too heavy on the client.
I am making a game in which I need to integrate Facebook leader-board and facebook friends invitation like in subway surfer. I used official facebook sdk and got it done. But the approach I am following is not efficient.
My friend invite screen has its own UI so I could not use the default popup comes with facebook SDK.
I fetch all the friends list from graph api It provides me the list of all IDs of users . But I needed to get name and images of friends to show in a scroll list . I am fetching the images from url asynchronously but whenever I try to fetch the image my scroll list hangs up . here is my code
IEnumerator start ()
{
url ="http://profile.ak.fbcdn.net/hprofile-ak-prn2/t5/1116929_100003613987476_290892640_q.jpg";
WWW www = new WWW(url);
yield return www;
var texture = www.texture;
this.gameObject.GetComponent<UITexture>().mainTexture = texture;
}
I am using coroutines, but I dont know why my scroll hang up on scrolling if these are asynchronous ? The code is been taken form here . I have checked Threading but www can not be used in threads. I just want to smooth my scrollview which I made by using NGUI sdk during image fetching.
Problem 1 - How can I make smooth scrolling by asynchronous image download simultaneously.
Problem 2 - I am loading each image into UITexture of NGUI, which I instantiate at run time. But if one have 200 or above friends then my app's memory reach at apex point and eventually app crashes. Is there any other way to load large number of images so that they take reasonable memory .
Scroll is working smoothly during web call on editor but not on device (iOS). Why ?
Please guys help me to smooth my scroll view and memory issue. Any help shall highly be appreciated. Thakks :)
This is a common problem on devices for any list that has more than 50 items that include images. You need to control two things: initializing list items and downloading assets.
You do not need to load everything at once and display them. I would recommend breaking this down into three parts:
1. Store friend list data
Fetch all the user's Facebook friends and store it in a list. This does not include downloading the profile pictures, only the basic information and the URL of their profile picture. We will download the picture at a later step.
2. Pool list items
Don't create more than triple the list items you can display at any given time. While scrolling, you'll simply reuse the ones already created like so:
You should only need to do the third step in that picture only when the user has lifted his finger and the list has enough momentum to continue scrolling. Otherwise you should have enough items loaded in memory to not require this when the user simply drags the list.
When moving a list item either at the end or the beginning (depending on the direction of the scroll) you should load the friend's data. You still do not load profile pictures at this step.
3. Queuing image requests
You can use Unity's OnBecameVisible method to figure out when an item is currently visible. But generally you'll need a manager that keeps track of what items are visible at any given moment.
Every time the scroll list has no momentum and the user is not dragging it, you queue up requests to fetch the images for the items that are currently visible. This should be handled by a different manager that does the following:
Keeps N most recent profile pictures in memory to avoid loading them every time
Checks to see if the image is already downloaded by using LoadFromCacheOrDownload as Roberto suggested.
Makes sure that the item the request was made for is still visible before setting the texture (otherwise it keeps the image in memory)
Of course, this is very high level. There are many things to implement here and in many different ways. But I hope you get the idea.
I was thinking about this when I asked about starting many coroutines at the same time.
Well you just can't load them all at the same time and expect the app to run smoothly, the hardware is just not able to handle this.
You can made a loading screen or something similar where the user will have to wait to see/use the scroller (though I've never seen a game doing this for loading Facebook pictures) or you will have to give up in showing them all at the same time and create a pool of a small number of WWW connections that you can use at the same time, that is, limit the number of images being loaded at the same time, and only start loading a new image when one has finished.
Now one thing that can certainly speed things up - not in the first time that you run it, but subsequent runs - is to use WWW.LoadFromCacheOrDownload() instead of new WWW(). This method will store the pictures that were downloaded in the past and load them instead of redownloading. This is how every Unity game I've ever seen do integration with Facebook.
We have a Universal iOS app. We use JSON to download content into the app during the first launch.
The size of content being downloaded on iPad (due to large image sizes) is about 100MB and on iPhone (smaller images) 80MB.
It takes about 4-7 minutes to download all content over WiFi. On 3G it takes 7-10 minutes.
Images are large in size, text content is not so much.
Is there any way we could reduce the download time? I'm aware of JSON compression but not sure if it will help with images?
Any ideas?
Tx
Some of this information has been mentioned in various comments, but it's what I would have suggested anyway and I figured having an answer written up would be more useful, so here goes:
Instead of storing the location of each image in the JSON file and downloading them one at a time, store the images in zip files and download those. Any amount of compression you get from this is an improvement in your download time, and you can simply unzip the images once you've downloaded the zip files.
Store a reasonable amount of the images in the main bundle along with the app. In your case you clearly can't store them all there; you don't want to have a huge app, and since it's a recipe app I presume you'll be adding more recipes as you go along. It makes a lot more sense to be able to update and download recipes via the JSON than to have to push a new version of the app to the app store with each change. But there are some items that can be bundled with the app; ex. large background images, design elements that are constant and unlikely to change. Any image which won't be likely to need updating should be bundled with the main app; any savings on download time is an improvement.
Have some kind of interesting loading screen. This is very important since you're talking about 4-10 minute download times. That is a long time for a user. When engaging with an application, 30 seconds can even seem like a long time. You're going to be hard pressed to make me willing to sit there and wait for 10 minutes for the app to begin if nothing is happening during that time. Have creative vegetable characters move across the screen, include a little interactive puzzle or ingredients on the loading screen, something. Just give the user something to look at (or preferably something interactive to do) while they're waiting. Otherwise you're probably going to lose a lot of users during this download.
If you can make this work, it really is a good idea to only download items when necessary. For example if your recipe app is broken into "Salads", "Fish", "Chicken", "Desserts", etc., you could prompt the user to download the recipe information for each category the first time they click on it. The great thing about this is that it breaks up the time the user has to spend waiting on the download; let's say you have 10 recipe categories, now the time the user has to wait all at once has been decreased from 4-10 minutes to 30 seconds-1 minute. That is a huge difference. I'll wait 1 minute for the Dessert recipes (still give them something to do!) much more readily than I'll wait 10 minutes for the whole app.
4b. You mentioned not being able to access new content while offline, which is a valid concern. To address this you could have a prompt the first time the app loads: This application contains hundreds of exciting recipes and delicious images. It can take several minutes to download all this yummy information. You can download all the content now, or proceed to the app and download recipes by category when you wish to view them. Please note that this will require an internet connection. And the buttons could say something like Download Now and Proceed to App. Obviously you can play with the wording to your heart's content, but the idea is sound. You've informed the user that there might be a significant download time, so at least they're forewarned, and they can make their own decision about how to use your app.
However you decide to proceed, break up the content you download into logical categories for the zip files. For example, "Desserts.zip", "Salads.zip", etc. That way even if you don't decide to let the user download on-demand now, you've set up a structure that is open to change later on.