I'm seeking a tutorial on how to cache images loaded from a url into cells of a uitableview.
I found an example here
http://www.ericd.net/2009/05/iphone-caching-images-in-memory.html#top
But the code is incomplete. I'm an objective c novice so I found it very difficult to fill in the missing pieces.
Here is a simple ImageCache implementation using NSCache. ImageCache is a singelton.
ImageCache.h
#import <Foundation/Foundation.h>
#interface ImageCache : NSObject
#property (nonatomic, retain) NSCache *imgCache;
#pragma mark - Methods
+ (ImageCache*)sharedImageCache;
//- (void) AddImage:(NSString *)imageURL: (UIImage *)image;
- (void) AddImage:(NSString *)imageURL withImage:(UIImage *)image;
- (UIImage*) GetImage:(NSString *)imageURL;
- (BOOL) DoesExist:(NSString *)imageURL;
#end
ImageCache.m
#import "ImageCache.h"
#implementation ImageCache
#synthesize imgCache;
#pragma mark - Methods
static ImageCache* sharedImageCache = nil;
+(ImageCache*)sharedImageCache
{
#synchronized([ImageCache class])
{
if (!sharedImageCache)
sharedImageCache= [[self alloc] init];
return sharedImageCache;
}
return nil;
}
+(id)alloc
{
#synchronized([ImageCache class])
{
NSAssert(sharedImageCache == nil, #"Attempted to allocate a second instance of a singleton.");
sharedImageCache = [super alloc];
return sharedImageCache;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
imgCache = [[NSCache alloc] init];
}
return self;
}
// - (void) AddImage:(NSString *)imageURL: (UIImage *)image
- (void) AddImage:(NSString *)imageURL withImage:(UIImage *)image
{
[imgCache setObject:image forKey:imageURL];
}
- (NSString*) GetImage:(NSString *)imageURL
{
return [imgCache objectForKey:imageURL];
}
- (BOOL) DoesExist:(NSString *)imageURL
{
if ([imgCache objectForKey:imageURL] == nil)
{
return false;
}
return true;
}
#end
Example
UIImage *image;
// 1. Check the image cache to see if the image already exists. If so, then use it. If not, then download it.
if ([[ImageCache sharedImageCache] DoesExist:imgUrl] == true)
{
image = [[ImageCache sharedImageCache] GetImage:imgUrl];
}
else
{
NSData *imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: imgUrl]];
image = [[UIImage alloc] initWithData:imageData];
// Add the image to the cache
//[[ImageCache sharedImageCache] AddImage:imgUrl :image];
[[ImageCache sharedImageCache] AddImage:imgUrl withImage:image];
}
A nice working example was found here
http://ezekiel.vancouver.wsu.edu/~wayne/yellowjacket/YellowJacket.zip
You could also try using the awesome EgoImage library written by the sharp fellows at enormego to accomplish this. It is very simple to use, makes efficient use of cache behind the scenes and is ideally suited to meet your requirements.
Here's the github path for the library which includes a demo app.
I wrote this (with concepts and some code taken from Lane Roathe's excellent UIImageView+Cache category) for an app I've been working on. It uses the ASIHTTPRequest classes as well, which are great. This could definitely be improved.. for example, by allowing requests to be canceled if no longer needed, or by utilizing the notification userInfo to allow for more precise UI updating.. but it's working well for my purposes.
#implementation ImageFetcher
#define MAX_CACHED_IMAGES 20
static NSMutableDictionary* cache = nil;
+ (void)asyncImageFetch:(UIImage**)anImagePtr withURL:(NSURL*)aUrl {
if(!cache) {
cache = [[NSMutableDictionary dictionaryWithCapacity:MAX_CACHED_IMAGES] retain];
}
UIImage* newImage = [cache objectForKey:aUrl.description];
if(!newImage) { // cache miss - doh!
ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:aUrl];
imageRequest.userInfo = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:anImagePtr] forKey:#"imagePtr"];
imageRequest.delegate = self;
[imageRequest setDidFinishSelector:#selector(didReceiveImage:)];
[imageRequest setDidFailSelector:#selector(didNotReceiveImage:)];
[imageRequest startAsynchronous];
}
else { // cache hit - good!
*anImagePtr = [newImage retain];
}
}
+ (void)didReceiveImage:(ASIHTTPRequest *)request {
NSLog(#"Image data received.");
UIImage **anImagePtr = [(NSValue*)[request.userInfo objectForKey:#"imagePtr"] pointerValue];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *newImage = [[UIImage imageWithData:[request responseData]] retain];
if(!newImage) {
NSLog(#"UIImageView: LoadImage Failed");
}
else {
*anImagePtr = newImage;
// check to see if we should flush existing cached items before adding this new item
if( [cache count] >= MAX_CACHED_IMAGES)
[cache removeAllObjects];
[cache setValue:newImage forKey:[request url].description];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName: #"ImageDidLoad" object: self userInfo:request.userInfo];
}
[pool drain];
}
You call this code as follows:
[ImageFetcher asyncImageFetch:&icon withURL:url];
I'm also using notifications, for better or worse, to let any owners of the corresponding UIImage know when they should redisplay- in this case, it's in a tableView context:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(imageDidLoad:) name:#"ImageDidLoad" object:nil];
}
- (void)imageDidLoad:(NSNotification*)notif {
NSLog(#"Received icon load notification.");
// reload table view so that new image appears.. would be better if I could
// only reload the particular UIImageView that holds this image, oh well...
[self.tableView reloadData];
}
- (void)dealloc {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
// ...
}
You also might wanna check HJCache. It comes with a UIImageView compatible view class that does all the caching transparently and is suitable to be used in UITableViewCells where scrolling performance is important.
Related
I have a sample app that uses AudioKit to record audio and display a waveform of that audio data. This sample app has two viewControllers with the root vc being a blank page with a button that will take the user to the audio recording page.
For some reason, only on iPhone X (iOS 11.4.1), while recording audio, if I hit the back button on the navigation bar (top left) and then try to go and record again the app will crash.
Specifically the app appears to crash when the recorder's method appendDataFromBufferList: withBufferSize: calls ExtAudioFileWrite(self.info->extAudioFileRef, bufferSize, bufferList). The error message that is printed in the console is:
testAudioCrash(1312,0x16e203000) malloc: * **error for object 0x109803a00: incorrect checksum for freed object - object was probably modified after being freed.
* **set a breakpoint in malloc_error_break to debug
I've gone through zombie profiling, leak profiling, stepped through the logic and the stack but I can't seem to figure out why this is happening.
Below i've provided the code for the test app as well as screenshots of the stack and the console output. Any help with figuring out why this is crashing would be greatly appreciated. Unfortunately the fact that this crash is also not 100% reproducible makes it a little more obscure to me.
Notes for code below:
There is no custom code in the .h files so I have not provided that. There are xib files for each view controller with the UI components for this. They're pretty simple so I have not provided information on those as well though I have no problem in providing any information on them, that anyone requests. I can also zip up the project and share that if anyone feels it's necessary.
Repro steps:
1) launch app
2) tap on record Audio button
3) tap on record button
4) hit back button on navigation bar
5) repeat steps 2-4 until crash happens
AppDelegate.m code:
#import "AppDelegate.h"
#import "testViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
testViewController* rootVC = [[testViewController alloc] initWithNibName: #"testViewController" bundle: NSBundle.mainBundle];
UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController: rootVC];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
testViewController.m code:
#import "testViewController.h"
#import "testSecondViewController.h"
#interface testViewController ()
#end
#implementation testViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)AudioRecording:(id)sender
{
testSecondViewController* sVC = [[testSecondViewController alloc] initWithNibName: #"testSecondViewController" bundle: NSBundle.mainBundle];
[self.navigationController pushViewController: sVC animated: YES];
}
#end
testSecondViewController.m code:
#import "testSecondViewController.h"
#import AudioKit;
#import AudioKitUI;
#interface testSecondViewController () <EZMicrophoneDelegate, EZRecorderDelegate>
#property (nonatomic, strong) EZRecorder* recorder;
#property (nonatomic, strong) EZMicrophone* mic;
#property (nonatomic, strong) EZAudioPlayer* player;
#property (strong, nonatomic) IBOutlet EZAudioPlot *audioPlot;
#property (nonatomic, strong) NSURL *finishedRecordingURL;
#property (atomic, assign) BOOL isRecording;
#end
#implementation testSecondViewController
- (void)dealloc
{
if(_isRecording) [self pauseRecording: _mic];
if(_recorder) [self finalizeAudioFile: _recorder];
_recorder.delegate = nil;
_mic.delegate = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[EZAudioUtilities setShouldExitOnCheckResultFail: NO];
[self setupUI];
[self setupConfig];
[self audioKitSetup];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UI Methods
-(void)setupUI
{
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style: UIBarButtonItemStylePlain target: nil action:#selector(cancelButtonClicked)];
[self configureWaveFormViewForAudioInput];
}
-(void)setupConfig
{
[self initializeMic];
[self initializeRecorder];
}
-(void)initializeMic
{
self.mic = [[EZMicrophone alloc] initWithMicrophoneDelegate: self];
self.isRecording = NO;
}
-(void)initializeRecorder
{
NSURL *fileUrl = [self testFilePathURL];
self.finishedRecordingURL = fileUrl;
self.recorder = [[EZRecorder alloc] initWithURL: fileUrl clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
}
#pragma mark - Utils
- (NSArray *)applicationDocuments
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
}
- (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
- (NSURL *)testFilePathURL
{
self.finishedRecordingURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#",
[self applicationDocumentsDirectory],
#"test2.m4a"]];
if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
if(error){
printf("%s", error.description);
}
}
return self.finishedRecordingURL;
}
#pragma mark AudioKit Util methods
- (void) audioKitSetup
{
[AKSettings setDefaultToSpeaker: YES];
[AKSettings setAudioInputEnabled: YES];
[AKSettings setPlaybackWhileMuted: YES];
[AKSettings setSampleRate: 44100];
[AKSettings setChannelCount: 1];
}
- (void) configureWaveFormViewForAudioInput
{
// self.audioPlot.gain = 6;
// self.audioPlot.color = [UIColor blueColor];
self.audioPlot.plotType = EZPlotTypeRolling;
// self.audioPlot.shouldFill = YES;
// self.audioPlot.shouldMirror = YES;
[self.view addSubview: self.audioPlot];
self.audioPlot.clipsToBounds = YES;
}
- (IBAction)startRecording:(id)sender
{
if (!self.mic)
{
self.mic = [EZMicrophone microphoneWithDelegate: self];
}
if (!self.recorder)
{
if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
{
self.recorder = [EZRecorder recorderWithURL: self.finishedRecordingURL clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
}
else
{
self.recorder = [EZRecorder recorderWithURL: [self testFilePathURL] clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
self.finishedRecordingURL = self.recorder.url;
}
}
[self.mic startFetchingAudio];
self.isRecording = YES;
}
- (IBAction)pauseRecording:(id)sender
{
[self.mic stopFetchingAudio];
self.isRecording = NO;
}
- (void) finalizeAudioFile: (EZRecorder*) recorder
{
if (self.isRecording)
{
[self.mic stopFetchingAudio];
}
[recorder closeAudioFile];
}
- (IBAction)cancelButtonClicked:(id)sender
{
if(self.isRecording)
{
[self pauseRecording: self.mic];
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle: #"Delete recording?" message:#"Would you like to delete your audio recording and stop recording?" preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"Discard"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self finalizeAudioFile: self.recorder];
NSError *error;
[[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
if(error){
printf("%s", error.description);
}
[self dismissViewControllerAnimated:YES completion:NULL];
}];
UIAlertAction* noButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[alert dismissViewControllerAnimated:YES completion: nil];
}];
[alert addAction:yesButton];
[alert addAction:noButton];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - EZMicrophone Delegate methods
- (void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
__weak typeof (self) weakling = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakling.audioPlot updateBuffer:buffer[0]
withBufferSize:bufferSize];
});
}
- (void) microphone:(EZMicrophone *)microphone
hasBufferList:(AudioBufferList *)bufferList
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
if (self.isRecording)
{
[self.recorder appendDataFromBufferList:bufferList
withBufferSize:bufferSize];
}
}
- (void)microphone:(EZMicrophone *)microphone changedPlayingState:(BOOL)isPlaying
{
self.isRecording = isPlaying;
}
#end
images:
I'm trying to implement background fetch as well as refresh in iOS 10.
I'm using XML parsing to parse the data and then storing it in a file in the document's directory. For parsing XML I'm using a custom class (XMLParser) that confirms the NSXMLParserDelegate protocol.
The background fetch works fine. But I'm having problems in displaying the refreshed data, both when I click on the refresh button as well as in viewDidLoad.
I'm calling the refreshData method in viewDidLoad.
Here's how far I've gotten.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//--Set background fetch--//
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
...
#pragma mark Background data fetch methods
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSDate *fetchStart = [NSDate date];
ArtsViewController *artsViewController = (ArtsViewController *)self.window.rootViewController;
[artsViewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
ArtsViewController.h
#interface ArtsViewController : UIViewController <UIPageViewControllerDataSource>
#property BOOL newsAvailable;
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; // No problems here
#end
ArtsViewcontroller.m
#interface ArtsViewController ()
#property (nonatomic, strong) NSArray *arrNewsData;
-(void)refreshData;
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;
#end
...
#implementation ArtsViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshData];
//--Load the file that saves news--//
[self loadNews];
if (_newsAvailable == YES)
{
[self setupPageViewController];
}
else
{
[self showNoNewsMessage];
}
}
...
#pragma mark Data Fetch methods
-(void)refreshData{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
[self performNewFetchedDataActionsWithDataArray:dataArray];
}
else{
NSLog(#"%#", [error localizedDescription]);
}
}];
}
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
// 1. Initialize the arrNewsData array with the parsed data array.
if (self.arrNewsData != nil) {
self.arrNewsData = nil;
}
self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];
// 2. Write the file and reload the view.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDirectory = [paths objectAtIndex:0];
NSString * newsFilePath = [NSString stringWithFormat:#"%#",[docDirectory stringByAppendingPathComponent:#"arts2"]]; // NewsFile
if (![self.arrNewsData writeToFile:newsFilePath atomically:YES]) {
_newsAvailable = NO;
NSLog(#"Couldn't save data.");
}
else
{
_newsAvailable = YES;
NSLog(#"Saved data.");
[self viewWillAppear:YES];
}
}
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
NSString *latestTitle = [latestDataDict objectForKey:#"title"];
NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
NSString *existingTitle = [existingDataDict objectForKey:#"title"];
if ([latestTitle isEqualToString:existingTitle]) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No new data found.");
}
else{
[self performNewFetchedDataActionsWithDataArray:dataArray];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New data was fetched.");
}
}
else{
completionHandler(UIBackgroundFetchResultFailed);
NSLog(#"Failed to fetch new data.");
}
}];
}
...
#pragma mark IBActions
- (IBAction)reloadNews:(UIBarButtonItem *)sender
{
[self viewDidLoad];
}
I've debugged the application and found that after viewDidLoad
completes execution, the data file is written but the view isn't
updated. I've also tried calling the refreshData method in the main
thread, but there's no change.
after viewDidLoad is complete the showNoNewNews method is called.
I'm suspecting that my logic isn't wrong but implementation is. Threads at play here..
Any help would be appreciated.
Update:
Hope this helps those with similar problems...
I moved the logic of viewDidLoad to a different method, called the method for the first time in viewDidLoad and again in refreshData, after
[self performNewFetchedDataActionsWithDataArray:dataArray];
So I'm pulling down about 50 images from my API using NSURLConnection, its working great, except its locking up the UI when it runs. I'm assuming that is because I'm updating the UI in real time form the NSURLConnection self delegate. So I'm thinking what I need to do is put placeholder loading images in the UIImage, then update them somehow once the delegate has acquired all the data, but how do I do that, can someone give me some coding examples?
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSData *imageData = _dataDictionary[ [connection description] ];
if(imageData!=nil)
{
NSLog(#"%#%#",[connection description],imageData);
UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(self.x, 0, self.screenWidth, self.screenHight)];
// Process thi image
// resize the resulting image for this device
UIImage *resizedImage = [self imageScaleCropToSize:[UIImage imageWithData: imageData ]];
self.x = (self.x + imageView.frame.size.width);
if(self.x > self.view.frame.size.width) {
self.scrollView.contentSize = CGSizeMake(self.x, self.scrollView.frame.size.height);
}
[imageView setImage:resizedImage];
// add the image
[self.scrollView addSubview: imageView];
}
}
You can use SDWebImage library to achieve this.
Suppose imageArray have all the image url path.
You can use SDWebImageManager to download all the images and show them in ImageView. Also you can show downloading progress using this block.
- (void)showImages:(NSArray *)imageArray
{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
for (NSString *imagePath in imageArray)
{
[manager downloadImageWithURL:[NSURL URLWithString:imagePath]
options:SDWebImageLowPriority
progress:^(NSInteger receivedSize, NSInteger expectedSize){}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
{
if(!error)
self.imgView_Image.image = image;
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"please check your Connection and try again" message:#"No Internet Connection" delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
[alert show];
}
}];
}
}
First create protocol in that class .h, where you call NSURLConnection request for download image (Where you implement this method connectionDidFinishLoading).
#protocol YourClassNameDelegate <NSObject>
- (void)didFinishLoadingImage:(UIImage *)downloadImage;
#end
and create property for that protocol in same class,
#interface YourViewController : UIViewController
#property (nonatomic, retain) id<YourClassNameDelegate>delegate;
#end
then synthesise it in .m, #synthesize delegate;
After that call didFinishLoadingImage: in connectionDidFinishLoading,
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSData *imageData = _dataDictionary[ [connection description] ];
if(imageData!=nil)
{
NSLog(#"%#%#",[connection description],imageData);
UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(self.x, 0, self.screenWidth, self.screenHight)];
// Process thi image
// resize the resulting image for this device
UIImage *resizedImage = [self imageScaleCropToSize:[UIImage imageWithData: imageData ]];
self.x = (self.x + imageView.frame.size.width);
if(self.x > self.view.frame.size.width) {
self.scrollView.contentSize = CGSizeMake(self.x, self.scrollView.frame.size.height);
}
[self.delegate didFinishLoadingImage:resizedImage];
[imageView setImage:resizedImage];
// add the image
[self.scrollView addSubview: imageView];
}
}
and finally from where you push to YourViewController set delegate to self, like :
YourViewController *controller = [[YourViewController alloc] init];
controller.delegate = self;
//.....
in YourViewController.m, where you want to set downloaded image, in that class implement this method.
#pragma mark - YourClassName delegate method
- (void)didFinishLoadingImage:(UIImage *)downloadImage
{
//yourImageView.image = downloadImage;
}
Is there a best practice or library that helps cache processed images (i.e. images that have been created while the app is running) in iOS? I use SDWebImage for images that I download, but in various places in the app I blur or in other ways process some of these images. I would like to store the processed images in a cache so that I can access them easily rather than reprocess each time a user opens that image. What's the best way to do this?
Thanks!
The answer it seems is using NSCache. It's quite straightforward to do. I ended up with a subclass of NSCache to make sure memory warnings are handled.
Implementation of NATAutoPurgeCache (heavily based on other posts on StackOverflow)
#implementation NATAutoPurgeCache
+ (NATAutoPurgeCache *)sharedCache
{
static NATAutoPurgeCache *_sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedCache = [[self alloc] init];
});
return _sharedCache;
}
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
#end
And using it when needed for an image: (in this case to store a blurred image)
UIImage* blurImage = [myCache objectForKey:#"blurred placeholder image"];
if (!blurImage)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage* blurImage = self.activityPic.image;
blurImage= [blurImage applyLightEffect];
dispatch_async(dispatch_get_main_queue(), ^{
self.activityPic.image = blurImage;
});
[myCache setObject:blurImage forKey:#"blurred placeholder image"];
});
}
else {
self.activityPic.image = blurImage;
}
I am trying to integrate SkyDrive in my iOS application and the following is the code where I have written the login authentication code. The client ID provided here is the one assigned for the application at the developer site. But the authentication always fails. Any idea what is the error ?
static NSString * const CLIENT_ID = #"00000000XXXXXXXXX";
#implementation PSMainViewController
#synthesize appLogo;
#synthesize userInfoLabel;
#synthesize signInButton;
#synthesize viewPhotosButton;
#synthesize userImage;
#synthesize liveClient;
#synthesize currentModal;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
_scopes = [NSArray arrayWithObjects:
#"wl.signin",
#"wl.basic",
#"wl.skydrive",
#"wl.offline_access", nil];
liveClient = [[LiveConnectClient alloc] initWithClientId:CLIENT_ID
scopes:_scopes
delegate:self];
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.appLogo.image = [UIImage imageNamed:#"skydrive.jpeg"];
[self updateUI];
}
- (void)viewDidUnload
{
[self setAppLogo:nil];
[self setUserInfoLabel:nil];
[self setUserImage:nil];
[self setSignInButton:nil];
[self setViewPhotosButton:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)signinButtonClicked:(id)sender {
if (self.liveClient.session == nil)
{
[self.liveClient login:self
scopes:_scopes
delegate:self];
}
else
{
[self.liveClient logoutWithDelegate:self
userState:#"logout"];
}
}
- (IBAction)viewPhotoButtonClicked:(id)sender {
// Create a Navigation controller
PSSkyPhotoViewer *aPhotoViewer = [[PSSkyPhotoViewer alloc] initWithNibName:#"PSSkyPhotoViewer" bundle:nil];
aPhotoViewer.parentVC = self;
self.currentModal = [[UINavigationController alloc] initWithRootViewController:aPhotoViewer];
[self presentModalViewController:self.currentModal animated:YES];
}
- (void) modalCompleted:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
self.currentModal = nil;
}
#pragma mark LiveAuthDelegate
- (void) updateUI {
LiveConnectSession *session = self.liveClient.session;
if (session == nil) {
[self.signInButton setTitle:#"Sign in" forState:UIControlStateNormal];
self.viewPhotosButton.hidden = YES;
self.userInfoLabel.text = #"Sign in with a Microsoft account before you can view your SkyDrive photos.";
self.userImage.image = nil;
}
else {
[self.signInButton setTitle:#"Sign out" forState:UIControlStateNormal];
self.viewPhotosButton.hidden = NO;
self.userInfoLabel.text = #"";
[self.liveClient getWithPath:#"me" delegate:self userState:#"me"];
[self.liveClient getWithPath:#"me/picture" delegate:self userState:#"me-picture"];
}
}
- (void) authCompleted: (LiveConnectSessionStatus) status
session: (LiveConnectSession *) session
userState: (id) userState {
[self updateUI];
}
- (void) authFailed: (NSError *) error
userState: (id)userState {
// Handle error here
}
#pragma mark LiveOperationDelegate
- (void) liveOperationSucceeded:(LiveOperation *)operation {
if ([operation.userState isEqual:#"me"]) {
NSDictionary *result = operation.result;
id name = [result objectForKey:#"name"];
self.userInfoLabel.text = (name != nil)? name : #"";
}
if ([operation.userState isEqual:#"me-picture"]) {
NSString *location = [operation.result objectForKey:#"location"];
if (location) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:location]];
self.userImage.image = [UIImage imageWithData:data];
}
}
}
- (void) liveOperationFailed:(NSError *)error operation:(LiveOperation *)operation
{
// Handle error here.
}
#end
May be you are not providing the redirect url in case of a desktop application.But if it's the mobile application the you have to check the "Mobile or Desktop client app: " as yes in the api settings