I'm trying to display an image from a webcam in my app using HJCache.
The webcam refreshes its image every 5 minutes.
I'd like to have a refresh button, so that users could click it to see a new image (if available).
My code so far:
-(void)viewDidLoad {
// init HJObjManager
objMan = [[HJObjManager alloc] initWithLoadingBufferSize:6 memCacheSize:20];
// refresh button
UIBarButtonItem *buttonRefresh = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:#selector(refreshPhoto:)];
self.navigationItem.rightBarButtonItem = buttonRefresh;
[buttonRefresh release];
NSURL *url = [NSURL URLWithString: #"http://webcamurl"];
img1.url = url;
[self.objMan manage:img1];
}
-(IBAction) refreshPhoto: (id) sender {
// ?
}
Could you give me an hint on how to implement refreshPhoto?
Edit: ender pointed me to emptyCache. If I understand it ok, it should be used by HJMOFileCache, so my code now is:
-(void)viewDidLoad {
NSString *documentsDirectory;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
documentsDirectory = [documentsDirectory stringByAppendingPathComponent:#"imageCache/"];
objMan = [[HJObjManager alloc] initWithLoadingBufferSize:6 memCacheSize:20];
HJMOFileCache* fileCache = [[[HJMOFileCache alloc] initWithRootPath:documentsDirectory] autorelease];
fileCache.fileCountLimit = 100;
fileCache.fileAgeLimit = 300; // 5 min
objMan.fileCache = fileCache;
// refresh button
UIBarButtonItem *buttonRefresh = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:#selector(refreshPhoto:)];
self.navigationItem.rightBarButtonItem = buttonRefresh;
[buttonRefresh release];
NSURL *url = [NSURL URLWithString: #"http://webcamurl"];
img1.url = url;
[self.objMan manage:img1];
[super viewDidLoad];
}
-(IBAction) refreshPhoto: (id) sender {
[self.objMan.fileCache emptyCache];
[self.objMan manage:img1];
}
It doesn't work though, when I click the refresh button nothing happens, the image does not refresh.
Any idea?
Edit: ender suggested that maybe the cache files do not get deleted by emptyCache (if I understand it right), but it looks like they actually do.
From NSLog before and after the emptyCache:
2011-09-09 16:57:33.842 Ready dir before emptyCache: (
"http:__www.meteogallipoli.it_cam_cam1.jpg"
)
2011-09-09 16:57:33.845 Loading dir before emptyCache: (
)
2011-09-09 16:57:33.856 Ready dir after emptyCache: (
)
2011-09-09 16:57:33.859 Loading dir after emptyCache: (
)
"Ready" and "Loading" are the directories where objMan stores files already downloaded and being downloaded, respectively.
Maybe the problem is in making objMan manage the image again?
I think its because you configured the object manager with both a file cache and a memory cache. When you empty the file cache, there are still images in the memory cache? Try instanciating the object manager with
if you only have an image, you can use [objMan emptyCache];
If you want to refresh only an image, you can save it in a different dir and use this method instead:
[objMan deleteAllFilesInDir:path];
Of course, you will have to make the query again:
[self.objMan manage:img1];
Related
I have an application which donwloads several images and stores them on the phone. In total it will probably required around 20 images tops. I need to be able to retrieve any of these images at will depending on what screen the user is on. These images will be stored indefinitely, so I don't want to use temp directory.
At present I have a class named Images with these methods
- (void) cacheImage: (NSString *) ImageURLString : (NSString *)imageName
{
NSURL *ImageURL = [NSURL URLWithString: ImageURLString];
// Generate a unique path to a resource representing the image you want
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: imageName];
// Check for file existence
if(![[NSFileManager defaultManager] fileExistsAtPath: docFile])
{
// The file doesn't exist, we should get a copy of it
// Fetch image
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
// Is it PNG or JPG/JPEG?
// Running the image representation function writes the data from the image to a file
if([ImageURLString rangeOfString: #".png" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImagePNGRepresentation(image) writeToFile: docFile atomically: YES];
}
else if([ImageURLString rangeOfString: #".jpg" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[ImageURLString rangeOfString: #".jpeg" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImageJPEGRepresentation(image, 100) writeToFile: docFile atomically: YES];
}
}
}
- (UIImage *) getCachedImage : (NSString *)imageName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* cachedPath = [documentsDirectory stringByAppendingPathComponent:imageName];
UIImage *image;
// Check for a cached version
if([[NSFileManager defaultManager] fileExistsAtPath: cachedPath])
{
image = [UIImage imageWithContentsOfFile: cachedPath]; // this is the cached image
}
else
{
NSLog(#"Error getting image %#", imageName);
}
return image;
}
-(void)getImages
{
//example
NSString *image1URL = #"http://test/image1.png";
NSString *image2URL = #"http://test/image2.png";
NSString *image3URL = #"http://test/image3.png";
[self cacheImage:sLogo: #"Image1"];
[self cacheImage:sBlankNav: #"Image2"];
[self cacheImage:buttonLarge :#"Image3"];
}
-(void) storeImages
{
image1 = [self getCachedImage:#"Image1"];
image2 = [self getCachedImage:#"Image2"];
image3 = [self getCachedImage:#"Image3"];
}
So I use the code like this
Images *cache = [[Images alloc]init];
[cache storeImages];
The get images method is called once when the app first starts to get the images, it isn't called again after that, unless the images on the server are updated and I need to retrieve the updated ones.
The code works, but the problem is when I navigate to a screen that uses it, there is a very slight delay before the screen loads as it is loading the images.
My application is a tabbed application, so it begins on tab 1, I click tab 2 which implements the code, there will be a slight pause the first time it loads. It doesn't last very long, but it is noticeable and is very annoying. After that it is fine, as it is already loaded. However with navigation controller, every time you move from the first VC to the second VC, the method will be called again, so each time you navigate the delay will be there.
The images are not very big, biggest one is 68kb, others are much smaller than that. At present I am just testing with 5 images. Is there a more efficient way of storing and retrieving images, or am I doing something wrong with my code? I need to be able to retrieve these images without any noticeable delay in order for my application to remain fluid and not jerky or clunky.
Thanks in advance!!
You have two options to do the image loading work on a background thread - use Grand Central Dispatch or NSInvocationOperation. GCD might be considered the cleaner of the two:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(q, ^{
//load images here
dispatch_async(main, ^{
// show on main thread here
});
});
you have delay because you're downloading data synchronously
// NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
Try some smart library like SDWebImage:
it lets you download image asynchronously while you still can display a local image (a proxy image). By the way, you still get cache image for free. So even if u are on local, you can still catch previously downloaded images
https://github.com/rs/SDWebImage
A must have
I have a UITextView in a DetailViewController with some text which is loaded from a NSMutableDictionary in the MainViewController. Here is the code for that;
- (void) coverflowView:(TKCoverflowView*)coverflowView coverAtIndexWasDoubleTapped:(int)index{
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
if ((int)index == 0) {
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"This is some text", #"textkey", nil];
detailViewController.myDictionary = myDictionary;
}
I have loaded the string from this into my DetailViewController with this code;
self.myText.text = [(NSMutableDictionary *)myDictionary objectForKey:#"textkey"];
Also in my viewDidLoad I have created a RightBarButton named 'Save' which I use to hide the keyboard when the viewer is done editing. I would like this button to also save the changes the viewer enters into the UITextView (as well as the original text).
This is the code for the rightBarButton;
UIBarButtonItem *saveButton =[[UIBarButtonItem alloc]
initWithTitle:#"Save"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(textViewDidChange:)];
self.navigationItem.rightBarButtonItem = saveButton;
Lastly I have code which envokes the textVidewDidChange and hides the keyboard. I am trying to also have it save the changes to the textView but it doesn't.
-(void)textViewDidChange:(UITextView *)textView;
{
if( textView == myText )
[myDictionary setValue:myText.text forKey:#"textkey"];
[myText resignFirstResponder];
}
Can anyone help me accomplish this. I simply want to save the changes to the UITextView back to the NSMutableDictionary. (or maybe not so simply)
I have changed the button to
`UIBarButtonItem *saveButton =[[UIBarButtonItem alloc]
initWithTitle:#"Save"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(didChangeValueForKey:)];
self.navigationItem.rightBarButtonItem = saveButton;`
and other code to ;
`-(void)didChangeValueForKey:(NSString *)key{
NSError *error;
[myDictionary setValue:[self myText].text forKey:#"textkey"];
[myText resignFirstResponder];
NSLog(#"%#", [myDictionary valueForKey:#"textkey"]);
}
My NSLog shows the changes but when I reopen the app they are gone, not saved. Can one save directly to the NSMutableDictionary?
I read a lot on persistent data. Thought NSData or Plist but try as I may not doing well.
Can someone suggest a good tutorial on this?
I looked at suggested link and added this (bold part) to my code.
`-(void)didChangeValueForKey:(NSString *)key{
/* NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:[(NSMutableDictionary *)myDictionary objectForKey:#"textkey"]];*/
NSError *error;
[myDictionary setValue:[self myText].text forKey:#"textkey"];
**[myDictionary writeToFile:#"textkey" atomically:YES];**
[myText resignFirstResponder];
NSLog(#"%#", [myDictionary valueForKey:#"textkey"]);
}`
As you can see I also tried getting the path using [NSHomeDirector] above and then replaced
#"textkey" with path. I can still see the changes in NSLog (either way) but there is no change when I reload the view or relaunch the app.
I have change things so that I am saving a text file of the text in the textview with the name gotten from the dictionary so that each time a different detailView is loaded depending on the image selected in the mainViewController.
This is my dictionary entry in the mainViewController;
`if ((int)index == 0) {
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
//here is where I want to access the text file name to load in the UITextView in my DetailView
#"first View Text", #"textkey2",
//This is where I give the text file of the changed text its name for each different view of the DetailView
#"first View Text", #"textkey3",nil];
detailViewController.myDictionary = myDictionary;
}`
Next is the code I use to save the changes to the textView using the UIbarbuttonright
`- (void)saveAction:(id)sender {
[self.myText2 resignFirstResponder];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
NSString *documentTXTPath = [documentsDirectory stringByAppendingPathComponent:[(NSMutableDictionary *)myDictionary objectForKey:#"textkey3"]];
NSString *savedString = myText2.text;
NSLog(#"The Save file is:%#", savedString);
[savedString writeToFile:documentTXTPath atomically:YES
encoding:NSUTF8StringEncoding error:NULL];
}`
I have checked this and the file is saved in the documents folder under the name of First View Text and it does contain the changed text.
I am having problems loading the text file contents into the UITextView.
Using the code I have I get the path to the textkey2 object (First Text View) not the contents of the file.
`NSString *textName = [myDictionary objectForKey:#"textkey2"];
NSArray *paths2 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory2 = [paths2 objectAtIndex:0];
NSString *fullPath2 = [documentsDirectory2 stringByAppendingPathComponent:textName];
/* self.myText2.text = fullPath2;*/
self.myText2.text = [NSString stringWithContentsOfFile:fullPath2 encoding:NSUTF8StringEncoding error:NULL];`
I replaced this last line with the one that isn't commented out and it works fine. For anyone who want to know.
You need to implement data persistence, e.g. saving the text into permanent storage that you can retrieve again when you reload the view later on.
I'm developing this application on an iPad.
It has a function that allows user to store images from the iPad's photo gallery into the application via NSDocumentDirectory.
There's a browse button, confirm button and one imageview in the first screen.
The browse button will display the photos from the iPad's photo gallery and allows the user to select a photo.
The confirm button will store the selected photo into NSDocumentDirectory.
Browse button:
- (IBAction) browsePhoto:(id)sender
{
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:imagePickerController];
[popover setPopoverContentSize:CGSizeMake(320,320)];
[popover presentPopoverFromRect:CGRectMake(200,200,-100,-100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
self.popoverController = popover;
[imagePickerController release];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)selectedImage editingInfo:(NSDictionary *)editingInfo
{
[self.popoverController dismissPopoverAnimated:YES];
imageView.image = selectedImage;
}
Confirm button:
- (IBAction) confirmPhoto:(id)sender
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:#"SavedImage.png"];
UIImage *image = imageView.image;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
}
I used these codes to try out on ONE image, so it will be working perfectly fine.
But after some time, i found out that i have to allow the user to store more than one photos. My codes are unable to do that because i somehow 'hardcode' the file name which is SavedImage.png and the new image will simply replace the old image.
I have thought of a method to solve this, which is by using a counter to name the images when i store them into my application.
Something like that:
NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"%d.png", counter]];
But this will not be a good naming convention for my images. I want to save them with proper names. What i have thought of, is that when the user tap on the confirm button, the application will display a message box that prompts and allows user to key in the name of the image that's being selected. The image will then be saved as the name that the user have entered.
Create a little ViewController with an UITextField and 2 buttons for OK and ABORT and show it with a UIPopOverController. You should be aware, that you must check for legal filenames, check for files already existing (and allow overwriting).
I am trying to show an indicator view when loading data in viewDidLoad but the loading starts before i display the indicator, i guess that is because the view is not loaded until after. I have been reading a bit about this but just cannot get it to work.
What i would want is to display the activity indicator during the loading of the files into the database.
I have been doing testing so the code structure may look a bit weird.
Could someone nice please give me a hint, or a link, how to fix this so the activity indicator is shown when/if data is loaded from the .txt files into the DB?
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #" ";
loadActivityIndicator.hidden = TRUE; // Hide UIActivityIndicationView at start
[self loadDataIfNeeded];
}
-(void)loadDataIfNeeded {
NSFileManager *fileManager = [NSFileManager defaultManager];
myFileArray = [[NSMutableArray alloc]init];
//======SET THE FILE INPUT NAME======//
qFileTxtName = #"110615";
[myFileArray addObject:qFileTxtName];
//===================================//
// CHECK IF THE FILE EXISTS
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"checkQuestionFile.plist"];
if([fileManager fileExistsAtPath:path]) {
NSArray *readKeyFileName = [[NSArray alloc] initWithContentsOfFile:path];
if ([[readKeyFileName objectAtIndex:0] isEqualToString:qFileTxtName]) {
//==== THERE IS NO UPDATED QUESTION .txt FILE ====//
}
else {
//==== THERE IS AN UPDATED QUESTION .txt FILE ====//
// SET UP UIActivityIndicator view to show that work is ongoing
loadActivityIndicator.hidden = FALSE;
[loadActivityIndicator startAnimating];
//==== SET UP PATH FOR ALL FILES====//
NSString *aString = [[NSString alloc]init];
aString = [#"questions_famquiz_hard_" stringByAppendingString:qFileTxtName];
NSString *path1 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
aString = [#"questions_famquiz_medium_" stringByAppendingString:qFileTxtName];
NSString *path2 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
aString = [#"questions_famquiz_easy_" stringByAppendingString:qFileTxtName];
NSString *path3 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
AccessQuestionsDB *accessQuestionDataFunction = [AccessQuestionsDB new];
idCounter = [accessQuestionDataFunction populateTheDatabase: path1 theID:0 firstTime: YES];
idCounter = [accessQuestionDataFunction populateTheDatabase: path2 theID:idCounter firstTime: NO];
idCounter = [accessQuestionDataFunction populateTheDatabase: path3 theID:idCounter firstTime: NO];
//==UPDATE THE PLIST==//
[myFileArray writeToFile:path atomically: TRUE];
// Stop UIActivityIndicator as activity is over
loadActivityIndicator.hidden = TRUE;
[loadActivityIndicator stopAnimating];
}
} else {
//== If file not found write a new file ==//
[myFileArray addObject:qFileTxtName];
[myFileArray writeToFile:path atomically: TRUE];
}
}
PROBLEM SOLVED
I replaced the call
[self loadDataIfNeeded];
with;
[NSThread detachNewThreadSelector:#selector(loadDataIfNeeded) toTarget:self withObject:nil];
to achieve multi threading according to the recommendation from Jonah :-)
You are loading your data synchronously in the main thread. That means that the main thread, the one responsible for drawing the UI, is busy loading your data and will not have a chance to update your view until after your loadDataIfNeeded method finishes (at which point you don't want to show your activity indicator to be visible anymore anyway).
Display your activity indicator on the main thread but then allow the main thread's run loop to continue and instead perform expensive operations (like loading data or performing network requests) asynchronously on a secondary thread.
Look at NSThread, NSObject's -performSelectorInBackground:withObject:, and NSOperationQueue for different options for performing tasks off of the main thread.
The book "IOS Recipes" by Matt Drance has a recipe that talks about how to do this best. You can get this recipe as a free excerpt from the book at http://media.pragprog.com/titles/cdirec/activity.pdf
You can find the source code for the recipe on the book page at http://pragprog.com/titles/cdirec/ios-recipes
In your viewDidLoad add
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.frame = CGRectMake(0, 0, 44, 44);
activityView.center = self.view.center;
activityView.tag = 99;
[activityView startAnimating];
[self.view addSubview: activityView];
[activityView release];
Then in your load method after you finish loading just remove your activity indicator from super view.
So in your loadDataIfNeeded after loading add
[[self.view viewWithTag:99] removeFromSuperview];
Please help me !
I make an app like a slide show with back, previous button to change Image one by one. But when I using large image it crash. Now I'm using 64 jpg pics , size~20kb/1pic.
Help me or I die ! (U can add nick hoangtuanfithou, please)
code like that :
//Init Image Array
-(void)InitImageArray1
{
myAnimationImages1 = [[NSMutableArray alloc] init];
for ( int i = 0; i < 24; i++ )
{
NSAutoreleasePool *poolArray=[[NSAutoreleasePool alloc] init];
NSString *fileName = [NSString stringWithFormat:#"001 (%d)",i];
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:#”jpg”]];
[myAnimationImages1 addObject:image];
[poolArray release];
}
}
//Change Image
self.mainImage.image = [self.myAnimationImages1 objectAtIndex:animationImageArrIndex];
see page control sample of apple,or see scrolling madness available at github.