MBProgressHUD not showing when reloading data - objective-c

I load a JSON when my app starts up.
MBProgressHUD correctly shows a spinner while the data is loading.
I also have a refresh button that triggers a reload of JSON - and I'd like it to show the spinner. Although the data is refreshed the spinner does not show.
Any idea what I'm doing wrong?
Here is the relevant code in my ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self fetchPosts];
}
- (IBAction)refresh:(id)sender {
[MBProgressHUD showHUDAddedTo:self.view animated:YES]; // NOT WORKING
[self refreshPosts];
}
- (void)fetchPosts
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString: #"http://mysite.com/app/"]];
NSError* error;
posts = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
});
}
- (void)refreshPosts
{
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString: #"http://mysite.com/app/"]];
NSError* error;
posts = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}

Did you try putting the entire code of refreshPosts (and not just the call to reloadData) inside a dispatch block? I would certainly try to see if it works. I think your data download is probably causing a UI freeze which might screw up the HUD.

Related

Could not get data from coredata

I am getting an image from photo library and saving it to core data and then showing it to an image view. This is correctly working. But when i run project again the image is not showing on my image view.
This is code for getting image from photo library
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissModalViewControllerAnimated:YES];
UIImage *img= [info objectForKey:#"UIImagePickerControllerOriginalImage"];
NSManagedObjectContext *context=[self managedObjectContext];
NSManagedObject *newContact=[[NSManagedObject alloc] initWithEntity:[NSEntityDescription entityForName:#"Contacts" inManagedObjectContext:context] insertIntoManagedObjectContext:context];
//newContact = [NSEntityDescription insertNewObjectForEntityForName:#"Images" inManagedObjectContext:context];
NSData *data=UIImageJPEGRepresentation(img,0.0);
[newContact setValue:data forKey:#"image"];
[self fetchData];
}
This is the code of fetchData() method.
-(void)fetchData
{
NSManagedObjectContext *context=[self managedObjectContext];
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Contacts"
inManagedObjectContext:context]];
NSError * error = nil;
NSArray * objects = [context executeFetchRequest:request error:&error];
if ([objects count] > 0) {
NSManagedObject *managedObject = objects[0];
data1 = [managedObject valueForKey:#"image"];
image2 = [[UIImage alloc]initWithData:data1];
_imageView.image=image2;
}
}
This is my viewDidLoad()
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchData];
// Do any additional setup after loading the view.
}
First you should save image path (not uiimage) to Core Data and then get the path from Core Data and load image to your imageView by this path.
And use Magical Record (https://github.com/magicalpanda/MagicalRecord) for managing Core Data and you will have no problem with it at all.
You modify your coredata context by setting an entity but you don't save the modifications. It is for this reason that it is not saved when you run your project again.
To fix it you must save your NSManagedObjectContext context after modifications by simply using save method.
So, at the end of your fetchData method, try to make :
[context save:nil];
(You can pass a NSError in parameter to get errors during save.)
do this after all modifications on coredata
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
To save:
[newChannelToCoreData setValue:UIImagePNGRepresentation(_currentChannelImage) forKey:#"image"];
To fetch:
_currentChannelImage = [UIImage imageWithData:[lastChannel valueForKey:#"image"]];

how do you refresh ABUnknownPersonViewController?

My problem: I have a ABUnknownPersonViewController which needs to get an image from an online database. I have implemented an NSSession to download the image. The thread adds the image to the ABRecordRef then adds it to the ABUnknownPersonViewController. When the controller is pushed on the stack, it doesn't show the image...therfore sadness ensues.
NSString *imageUrl = dict[#"mug"];
__block NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *urlData = nil;
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
urlData = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
newPerson = controller.displayedPerson;
ABPersonSetImageData(newPerson, (__bridge CFDataRef)(urlData), &anError);
controller.displayedPerson = newPerson;
if(urlData != nil) {
NSLog(#"I got here");
[self viewWillAppear:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.razeware.imagegrabber.imageupdated" object:(__bridge id)(newPerson)];
}
});
}];
[getImageTask resume];
**Oddly enough, if I choose "Create New Contact" the image appears (So, the thread is working?). If I click cancel on iPhone then the image appears on the ABUnknownPersonViewController. So, it seems like the controller just needs to be refreshed. How?
I've tried [[self view] setNeedsDisplay]; //ain't working'
Help please!
OK - This may not be the best practice. However, since you already have [self viewWillAppear:YES]; you can try calling [self viewWillDisappear:NO]; before viewWillAppear. This is likely to refresh the content.
Sourced from here.
I had a similar problem recently and got this from somewhere else. Try adding a dispatch sync after the async to the main queue like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
// your image fetch code here
});
});
I found a solution:
ABRecordRef cannot be updated in a thread. It must be updated on the main thread.

slow loading using initWithContentsOfUrl

I have a web service and I make HTTP calls to it from cocoa using this line of code:
NSData *imageData = [[NSData alloc] initWithContentsOfUrl:url options: NSDataReadingUncached error:&error];
Sometimes is it take 10 seconds, sometimes 30 seconds to load the picture from this URL.
I tried loading this URL from a normal browser and it takes 1-2 seconds.
I'm doing lazy loading and the problem is the time it takes to load the contents of this URL vs a normal browser. Both tests were done from the same network.
Download.m
NSError *error;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:self.photoRecord.URL options:NSDataReadingUncached error:&error];
NSMutableArray *jsonArray = [NSJSONSerialization JSONObjectWithData:imageData options:NSJSONReadingAllowFragments error:&myError];
NSMutableDictionary *pictureInfo = [jsonArray objectAtIndex:0];
NSString *picture;
picture = [pictureInfo valueForKey:#"Picture"];
NSData *base64Data = [[NSData alloc]initWithBase64Encoding:picture];
if (base64Data) {
UIImage *downloadedImage = [UIImage imageWithData:base64Data];
self.photoRecord.image = downloadedImage;
}
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];
Then on the main thread PictureController.m:
-(void) loadScrollViewWithPage:(NSUInteger)page{
....
NSIndexPath *myIndexPath = [NSIndexPath indexPathForRow:page inSection:0];
[self startOperationsForPhotoRecord:aRecord atIndexPath:myIndexPath];
}
- (void)startOperationsForPhotoRecord:(PictureRecord *)record atIndexPath:(NSIndexPath *)indexPath {
Download *imageD = [[Download alloc] initWithPhotoRecord:record atIndexPath:indexPath delegate:self];
[self.pendingOperations.downloadsInProgress setObject:imageD forKey:indexPath];
[self.pendingOperations.downloadQueue addOperation:imageD];
}
Then I have the delegate method to update the UIImageView when the download is done which is working just fine.
The size of the content being loaded is around 400kb.
Any ideas?

Background fetch not updating UI

I'm new to ios7 background fetch. Here is how I'm doing it.
This is what is going on in appDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *url = [[NSURL alloc] initWithString:#"http://sample.com/api/home/lastnews"];
OldJson = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
}
Note that here I'm storing the whole JSON file in a string so i can use to check for new content.
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//HomepageView is the view controller that is supposed to be updated
HomepageView *homepage = [[HomepageView alloc] init];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURL *url = [[NSURL alloc] initWithString:#"http://sample.com/api/home/lastnews"];
NewJson = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
completionHandler(UIBackgroundFetchResultFailed);
return;
}
if (![NewJson isEqualToString:OldJson])
{
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New Data : %#",NewJson);
}
else
{
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"Old Json : %#",OldJson);
}
}];
// Start the task
[task resume];
}
Now to test the whole process, i simply run the application. Then i send it to background and then when i have new content in the JSON file, i use the Simulate Background Fetch operation from the Debug menu. Then i open the application but nothing has changed.
First store your newJson in NSUserDefaults as seen below
if (![NewJson isEqualToString:OldJson])
{
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New Data : %#",NewJson);
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:NewJson forKey:#"newJson"];
}
Second in your View Controller add this in the ViewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refresh:) name: UIApplicationWillEnterForegroundNotification object:nil];
Add this to your viewWillAppear
-(void)viewWillAppear:(BOOL)animated{
[self loadData];
}
Create the loadData
- (void)loadData {
// contains information the ViewController makes use of
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
textView.text = [defaults objectForKey:#"newJson"];
}
Finally add your action
-(IBAction)refresh:(id) sender {
NSLog(#"I ran");
[self loadData];
}
I tested this and it works.
Three problems:
There's no guarantee that JSON data comes in UTF-8 format. Use [NSData dataWithContensOfURL:...]
Never, ever access a URL from the main thread. If the server doesn't respond, your app will be hanging.
Really bad: In your background fetch handler, you actually load the URL synchronously. And then you create a task to load it again. So you load it actually twice.

How to download image asynchronously using blocks?

I want to click a button to start downloading image and to update my UIImageView to new image once it is updated. The problem with my code is that it only downloads stuff, and not updates. It only updates if I click it again.
I want it to update the image some time in future, when the image is downloaded. How do I do that?
Edit: I have found the wrong code, changing it a bit helped and it all works.
Here comes another question - how do I simplify this code without turning it a mess? It looks excessive.
- (IBAction)getImage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/1.jpg"];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_sync(dispatch_get_main_queue(), ^{
self.image = [UIImage imageWithData:imageData];
});
});
});
self.imageView.image = self.image;
}
You are setting the imageView before the image is done downloading, you need to move the logic into the block. Also there is no reason for you to do an extra dispatch_sync inside of your dispatch_async.
- (IBAction)getImage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/1.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
//This is your completion handler
dispatch_sync(dispatch_get_main_queue(), ^{
//If self.image is atomic (not declared with nonatomic)
// you could have set it directly above
self.image = [UIImage imageWithData:imageData];
//This needs to be set here now that the image is downloaded
// and you are back on the main thread
self.imageView.image = self.image;
});
});
//Any code placed outside of the block will likely
// be executed before the block finishes.
}
Check out https://github.com/rs/SDWebImage
I use it to download images in the background with progress notification. It can be added simply to your project using Cocoapods (http://cocoapods.org).
There are several other async image loaders available on Cocoapods and GitHub if that doesn't work for you.
This is what I've been using although it doesn't provide any progress which I think is often useful. This is simple through.
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, NSData *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
completionBlock(YES,data);
NSLog(#"downloaded FULL size %lu",(unsigned long)data.length);
} else{
completionBlock(NO,nil);
}
}];
}