Updating Interface from Result of NSURL Request - iphone

I have an app that updates the interface depending on the result of a NSURL request. I have set it up so that the request is fired when my app comes into the foreground, and only if the current view controller is called "ProfileViewController".
My problem is that the interface locks up for a few seconds every time I bring the app back from the background. I am trying to fully understand main/background threads, but am not sure what I can do to make the app remain responsive while the NSURL check is being performed. Any assistance would be great! Thanks!
In my View Did Load Method:
-(void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appReturnsActive) name:UIApplicationDidBecomeActiveNotification
object:nil];
//additional code
}
Then in my App Returns Active Method:
- (void)appReturnsActive {
[myTimer invalidate];
myTimer = nil;
//ONLY WANT TO PERFORM THE UPDATE IF VIEWING THE PROFILE VIEW CONTROLLER
UIViewController *currentVC = self.navigationController.visibleViewController;
NSString * name = NSStringFromClass([currentVC class]);
if ([name isEqualToString:#"ProfileViewController"]) {
[activityIndicator startAnimating];
[activityIndicatorTwo startAnimating];
locationManagerProfile.delegate = self;
locationManagerProfile.desiredAccuracy = kCLLocationAccuracyBest;
[locationManagerProfile startUpdatingLocation];
}
}
Finally, in my Did Update Location Method, I get the distance between the user and the location. If the result is equal to 1, then I update the Interface to show different buttons. This is where the interface freezes up:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
[self performSelectorOnMainThread:#selector(buttonUpdate:)
withObject:NULL waitUntilDone:NO];
}
}
New Method:
-(void)buttonUpdate {
NSString *userLongitude =[NSString stringWithFormat:#"%.8f",
currentLocation.coordinate.longitude];
NSString *userLatitude = [NSString stringWithFormat:#"%.8f",
currentLocation.coordinate.latitude];
[locationManagerProfile stopUpdatingLocation];
NSString *placeLatitude = [[NSUserDefaults standardUserDefaults]
stringForKey:#"savedLatitude"];
NSString *placeLongitude = [[NSUserDefaults standardUserDefaults]
stringForKey:#"savedLongitude"];
NSString *distanceURL = [NSString
stringWithFormat:#"http://www.website.com/page.php?
lat1=%#&lon1=%#&lat2=%#&lon2=%#",userLatitude, userLongitude, placeLatitude,
placeLongitude];
NSData *distanceURLResult = [NSData dataWithContentsOfURL:[NSURL
URLWithString:distanceURL]];
NSString *distanceInFeet = [[NSString alloc] initWithData:distanceURLResult
encoding:NSUTF8StringEncoding];
if ([distanceInFeet isEqualToString:#"1"])
{
UIBarButtonItem *btnGo = [[UIBarButtonItem alloc] initWithTitle:#"Button 1"
style:UIBarButtonItemStyleBordered target:self
action:#selector(actionTwo)];
self.navigationItem.rightBarButtonItem = btnGo;
UIBarButtonItem *btnGoTwo = [[UIBarButtonItem alloc] initWithTitle:#"Button
Two" style:UIBarButtonItemStyleBordered target:self
action:#selector(actionOne)];
self.navigationItem.rightBarButtonItem = btnGoTwo;
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:btnGo,
btnGoTwo, nil];
}
}

Your CoreLocation delegate methods (e.g. "didUpdateToLocation", etc.) are all happening in the background, on secondary threads, while everything UI-related needs to happen on the main thread.
To fix your problem, you should modify your UI on the main thread. One of the handy foundation API's you can use is:
performSelectorOnMainThread:withObject:waitUntilDone:
To fix your problem, move your button-creating code into a separate method and call that new method via "performSelectorOnMainThread" from the old (being called on a separate thread via the CoreLocation delegate protocol) and you should be good to go.

NSData *distanceURLResult = [NSData dataWithContentsOfURL:[NSURL
URLWithString:distanceURL]];
is a synchronous call. It means your code will wait for the result of the network call to continue. You should make an Asynchronous request, with NSURLConnection (http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html) or AFNetworking.

Related

Show UIAlertView during UIActivity:activityViewController

I have a set of UIActivities where I prepare my data into a given format and then attach it to an email the user can send. I'm using a subclass of UIActivity and I'm doing all the work in -(void)activityViewController:
- (UIViewController *)activityViewController
{
[self.alert show];
NSString *filename = [NSString stringWithFormat:#"%#.gpx", self.activity.title];
__block MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
mailComposeVC.mailComposeDelegate = self;
[mailComposeVC setSubject:[NSString stringWithFormat:#"GPX export for %# activity", self.activity.title]];
[mailComposeVC setMessageBody:#"Generated with Slopes" isHTML:NO];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
CBCFileExporter *exporter = [[CBCFileExporter alloc] init];
NSData *exportContents = [exporter exportActivity:self.activity inFileFormat:CBCFileExportTypeGPX error:nil];
[mailComposeVC addAttachmentData:exportContents mimeType:#"application/gpx+xml" fileName:filename];
});
[self.alert dismissWithClickedButtonIndex:0 animated:YES];
return mailComposeVC;
}
The specific issue I'm running into is that the UIAlertView doesn't actually show until the dispatch_sync completes. I realize the dispatch_sync might(?) be blocking the main thread as it waits, but the problem is I need to wait until the attachment is generated before returning from that method call (MFMailComposeViewController docs say you can't add attachment once the view is presented).
How can I get an alertview to show while a non-trivial task the main thread has to wait for completion has to run?
Given that the mail view controller specifically disallows adding an attachment to the mail compose view controller once it has been presented, what you probably need to do here is create and present an "interstitial" view controller with an indeterminate progress indicator, start the export process in the background, and then when that process is complete, create and fully populate the mail compose view controller with the attachment, then present it.
That requirement that it be fully populated before being presented means that there won't be a simple "do this in the background and call me back" approach possible.
Ick.
For what it's worth, I had to give up (after 4 hours of fighting with all kinds of blocks, performOnThread, etc) on using the activityViewController method to directly return a UI and instead switch to the performActivity method. PerformActivity is supposed to be for UI-less activities, but it's the only async-compatable one.
I have to set my main ViewController (the one showing the activity sheet) as a delegate to the UIActivities, then call my delegate back with the message VC once the export is ready:
- (void)performActivity
{
__block UIAlertView *alert = [[UIAlertView alloc] init];
alert.title = #"Generating Export";
[alert show];
//get rid of the activity sheet now - can't present the mail modal if this is active
[self activityDidFinish:YES];
__block CBCGPXEmailActivity *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
CBCFileExporter *exporter = [[CBCFileExporter alloc] init];
NSString *filename = [NSString stringWithFormat:#"%#.gpx", weakSelf.activity.title];
NSData *exportContents = [exporter exportActivity:weakSelf.activity inFileFormat:CBCFileExportTypeGPX error:nil];
//dispatch after to make sure there was time to remove the action sheet
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
[mailComposeVC setSubject:[NSString stringWithFormat:#"GPX export for %# activity", weakSelf.activity.title]];
[mailComposeVC setMessageBody:#"Generated with Slopes" isHTML:NO];
[mailComposeVC addAttachmentData:exportContents mimeType:#"application/gpx+xml" fileName:filename];
[weakSelf.delegate showMailViewController:mailComposeVC];
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
});
}

Method is not getting called twice in IOS

I am working on Instagram integration in IOS.Every thing goes well.I am geting the feeds of the user and displaying them on tableview and also in scroll view.Here the user is allowed to refresh the page.While refreshing the method is not getting called and its getting crashed because of zero objects in array.I had used the following code to call the method.
-(IBAction)loginAction:(id)sender
{
AppDelegate* appDelegate_new = (AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate_new.instagram authorize:[NSArray arrayWithObjects:#"comments", #"likes", nil]];
if ([appDelegate.instagram isSessionValid]) {
// NSLog(#"ViewDidLoad Session Valid");
loginView.hidden=YES;
crossButton.hidden=YES;
settingsButton.hidden=NO;
noticeView.hidden=YES;
[self.view addSubview:feedsView];
// [self.logOutView removeFromSuperview];
self.feedsView.frame=CGRectMake(0, 0, 240, 300);
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"users/self/feed", #"method", nil];
[appDelegate.instagram requestWithParams:params
delegate:self];
}
}
The called method was like this
- (void)request:(IGRequest *)request didLoad:(id)result
{
[self performSelector:#selector(startspinner) withObject:nil afterDelay:0.1];
self.data = (NSMutableArray *)[result objectForKey:#"data"];
// NSLog(#"Data Count is %#",[self.data description]);
createdArray=[[NSMutableArray alloc]init];
//*****Here I am performing Json Parsing******//
}
I am calling the above request method again while refreshing
- (void)dropViewDidBeginRefreshing:(ODRefreshControl *)refreshControl
{
[createdArray removeAllObjects];
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"users/self/feed", #"method", nil];
[appDelegate.instagram requestWithParams:params
delegate:self];
[self performSelector:#selector(refreshData) withObject:nil afterDelay:5.0];
}
Please tell me where I am going wrong.Correction appreciated.Thanks in advance.
Try it....
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:#selector(showTime)
userInfo:NULL
repeats:YES];
- (void)showTime
{
NSLog(#"Method called");
}
Use NSTimer for call method frequently.
Hope i helped.

CoreLocation app working in Simulator but not on device

I am trying to run this program on iPhone 3GS with iOS 5. Program works fine on simulator but doesn't work on device. No error but the locationManager:didUpdateToLocation:fromLocation: delegate method doesn't get called when run on the device.
I just want to print location after every 30 seconds.
int main(int argc, char *argv[])
{
#autoreleasepool
{
iLocation *loc = [[iLocation alloc] init];
[loc start];
NSLog(#"In main");
}
return 0;
}
#interface iLocation : NSObject <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
-(void) start;
-(void) startAgain;
#end
#import "BIDAppDelegate.h"
#implementation iLocation
#synthesize locationManager;
-(void) start
{
NSLog(#"Enter in start");
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
if([CLLocationManager locationServicesEnabled])
{
NSLog(#"Location Services are enabled");
[locationManager startUpdatingLocation];
}
else
NSLog(#"Location Services are disabled");
NSLog(#"Exit from start");
}
-(void) startAgain
{
[locationManager startUpdatingLocation];
}
- (void)locationManager: (CLLocationManager *)manager
didUpdateToLocation: (CLLocation *)newLocation
fromLocation: (CLLocation *)oldLocation
{
NSLog(#"Enter in didUpdateToLocation");
NSString *latitudeString = [NSString stringWithFormat:#"%g\u00B0",
newLocation.coordinate.latitude];
NSLog(#"Latitude = %#", latitudeString);
NSString *longitudeString = [NSString stringWithFormat:#"%g\u00B0",
newLocation.coordinate.longitude];
NSLog(#"Longitude = %#", longitudeString);
NSString *horizontalAccuracyString = [NSString stringWithFormat:#"%gm",
newLocation.horizontalAccuracy];
NSLog(#"Horizontal Accuracy = %#", horizontalAccuracyString);
NSString *altitudeString = [NSString stringWithFormat:#"%gm",
newLocation.altitude];
NSLog(#"Altitude = %#", altitudeString);
NSString *verticalAccuracyString = [NSString stringWithFormat:#"%gm",
newLocation.verticalAccuracy];
NSLog(#"Vertical Accuracy = %#", verticalAccuracyString);
NSTimer *timer = [NSTimer timerWithTimeInterval:20.0
target:self
selector:#selector(startAgain)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
NSLog(#"Exit from didUpdateToLocation");
}
your Location service may be off in iPhone please on it and try again.
in iPhone->setting->Location Service set it ON.
NSDate *now = [[NSDate alloc] init];
i think u have not initialized the date for today's date. And are u sure wether an app without a window or view, will be loaded into device..?
I think there's a couple potential problems with the code posted.
First of all, I just commented about this on another question. You don't need to keep telling CLLocationManager to startUpdatingLocation. You can just make that call once, until you decide to tell it to stopUpdatingLocation. Every time it gives you the new location, via
locationManager:didUpdateToLocation:fromLocation
you save/use it. You can have a timer fire every 30 seconds if you like, and if you don't have a newer location, then use the one from the last 30 seconds. Or use locationManager.location to get the most recent location fix.
See Apple's docs on CLLocationManager's startUpdatingLocation:
Calling this method several times in succession does not automatically
result in new events being generated. Calling stopUpdatingLocation in
between, however, does cause a new initial event to be sent the next
time you call this method.
Secondly, from the NSLog output you show above, and your main program, it looks like your program is just exiting before the location manager can ever deliver any location data. I have a non-graphical jailbreak app that has a process that runs and uses CoreLocation, and my main program looks like this:
#import "LocationDaemon.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
LocationDaemon* daemon = [[LocationDaemon alloc] init];
// start a timer so that the process does not exit.
NSTimer* timer = [[NSTimer alloc] initWithFireDate: [NSDate date]
interval: 1.0
target: daemon
selector: #selector(run:)
userInfo: nil
repeats: NO];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer: timer forMode: NSDefaultRunLoopMode];
[runLoop run];
[daemon release];
[pool release];
[timer release];
return 0;
}
It looks to me like you don't add your timer until after you receive location data, which you never get to before the program exits. Try doing like I do above, and adding your timer earlier. I also call [runLoop run], after adding my timer. I think you have to do that, to keep your main thread looping. Your main program just calls start, which calls startUpdatingLocation, but those calls are not blocking. Fairly quickly, it gets done with that, and the program completes. I don't think having a retained CLLocationManager instance is enough to keep the program running.
I have no idea why it would work in the simulator, though :(
P.S. Don't be concerned that my timer defined above does not repeat. In the run: implementation, I believe it fires off another timer with a different interval, or selector. So, my program does in fact keep running.

images not being added to NSMutableDictionary, which is within an NSOperation

So I first coded all my methods in a viewcontroller with an NSOperationQueue. After doing some research and a lot of reading I realized i had to subclass my loadImage operation so that I may use isCancelled and cancelAllOperations. So I went ahead and created an nsoperation class and called it from my viewcontroller. ALl the methods are called, even the imageLoaded, but the NSMutableDictionary remains empty. I use the dictionary to populate my tableviewcells using the url as the Key. Also be aware that the operation call in the viewcontroller is within a method which is called by an NSInvocationOperation when the view loads.
#interface loadImages : NSOperation {
NSURL *targetURL;
}
#property(retain) NSURL *targetURL;
- (id)initWithURL:(NSURL*)url;
#end
implementation of nsoperation class which includes some other calls to resize the image
#implementation loadImages
#synthesize targetURL;
- (id)initWithURL:(NSURL*)url
{
if (![super init]) return nil;
[self setTargetURL:url];
return self;
}
- (void)dealloc {
[targetURL release], targetURL = nil;
[super dealloc];
}
- (void)main {
NSLog(#"loadImages.m reached");
StoriesTableViewController *stories = [[StoriesTableViewController alloc] init];
NSMutableDictionary *tempDict = stories.filteredImagesDict;
UIImage *myImage = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[self targetURL]]]autorelease];
UIImage *scaledImage = [[[UIImage alloc] init] autorelease];
CGRect frame = CGRectMake(100.0f, 100.0f, 180.0f, 180.0f);
UIImageView *myImageFrame = [[UIImageView alloc] initWithFrame:frame];
myImage = [[myImage croppedImage:[myImageFrame bounds]]retain];
scaledImage = [[myImage resizedImage:CGSizeMake(120.0f, 120.0f) interpolationQuality:kCGInterpolationHigh]retain];
[tempDict setValue:scaledImage forKey:[self targetURL]];
[stories performSelectorOnMainThread:#selector(imageLoaded:)
withObject:myImage
waitUntilDone:YES];
NSLog(#"targetURL %#",[self targetURL]);
NSLog(#"tempDict count: %d",tempDict.count);
[stories release];
[myImage release];
[myImageFrame release];
[scaledImage release];
}
creation of nsoperation on viewcontroller
for(int i=0;i<storyQuantity;i++) {
NSString *imageString = [[[storiesArray objectAtIndex:i] objectForKey: #"image"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // must add trimming to remove characters
NSURL *url = [NSURL URLWithString:imageString];
loadImages *imageOperation = [[loadImages alloc] initWithURL:url];
[queue_ addOperation:imageOperation];
[imageOperation release];
}
If this is running without exceptions and the dictionary is still empty, it most likely means that your value is nil.
This is a common problem with code like that where you have the result of one method going into the next. At any point if there is a problem, all the rest of the chain will not work.
To solve, I would start right above where you assigned the image to the dictionary. You can use either a breakpoint, or an NSLog to determine the value of the image at that point. I prefer to use an NSLog, but a break point would let you look at all the values at once. If scaledImage is nil, then check myImage. Keep going back up the chain until you find the point where the value goes from what you would expect, to nil.

How to call a method on background thread

in my app i am unzipping a zip file(in splash view controller) later i am displaying an image and firing a timer with 4 secs to change image of the previous image view. and later in view did appear i am firing another timer with 10 secs to call next view controller(home view controller).. and there i have a button to go Story view controller...in this i am using the unzipped data...its taking some time to unzip in this case. so the images r displaying later a few seconds...it fine here
the problem is... i called the unzipping on background thread so its displaying the images quickly and its moving to home view controller..while going to next view controller by clicking a button its crashing on device but its working fine in simulator..
can any body help me out please
Thanks.
code...
-(void)viewWillAppear:(BOOL)animated{
[self performSelectorInBackground:#selector(unXip) withObject:nil];
CGRect imageFrame = CGRectMake(0,20,320,460);
splashView.frame = imageFrame;
splashView.image = [UIImage imageNamed:#"Default.png"];
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(changeImg) userInfo:nil repeats:NO];
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(changeImg2) userInfo:nil repeats:NO];
}
-(void)unXip{
self.fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
self.documentsDir = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/temp", self.documentsDir];
NSString *filePathbmlg = [NSString stringWithFormat:#"%#/temp/bmlg", self.documentsDir];
NSString *updateURL = [[NSBundle mainBundle] pathForResource:#"bmlg" ofType:#"zip" inDirectory:#"res"];
[fileManager createDirectoryAtPath:filePath
withIntermediateDirectories:NO
attributes:nil
error:nil];
if([fileManager fileExistsAtPath:filePath]) {
NSLog(#"File exists at path: %#", updateURL);
} else {
NSLog(#"File does not exists at path: %#", updateURL);
}
NSLog(#"Checking update at : %#", updateURL);
NSLog(#"Checking filepath at : %#", filePath);
if(![fileManager fileExistsAtPath:filePathbmlg]) {
ZipArchive *zipArchive = [[ZipArchive alloc] init];
if([zipArchive UnzipOpenFile:updateURL]) {
if ([zipArchive UnzipFileTo:filePath overWrite:YES]) {
//unzipped successfully
NSLog(#"Archive unzip Success");
//[self.fileManager removeItemAtPath:filePath error:NULL];
} else {
NSLog(#"Failure To Unzip Archive");
}
} else {
NSLog(#"Failure To Open Archive");
}
[zipArchive release];
}
}
- (void)changeImg{
splashView.image = [UIImage imageNamed:#"screen2.png"];
}
- (void)changeImg2{
splashView.image = [UIImage imageNamed:#"screen3.png"];
}
- (void)viewDidAppear:(BOOL)animated{
[NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(pushCVC) userInfo:nil repeats:NO];
}
- (void)pushCVC{
homeViewController = [[HomeViewController alloc] initWithNibName:#"HomeView" bundle:nil];
[self presentModalViewController:homeViewController animated:YES];
}
It's kind of hard to answer this witout any sample code. One thing to be aware of when using background threads is to make sure that all code that updates the UI runs on the main thread.
Ok, I think this is probably a threading issue. Your unXip method is modifying something on a background thread and then you try to access that "something" from another thread (main thread if you access it from UI code). You need to implement some kind of locking/synchronization mechanism (e.g. NSLock or NSConditionLock).
It looks a bit strange that you are using a timer to call pushCVC. Now, I don't know exactly what your app does but wouldn't it be better to call pushCVC when the unzip has finished by adding this to the end of your unXip method:
[self performSelectorOnMainThread:#selector(pushCVC) withObject:nil waitUntilDone:NO];
and removing the timer.
thanks for your comments and answers....
The problem is the unzipping process is taking a long time...so if i click start button on the home screen before the completion of the unzipping the file, its crashing...because i have to use the data from unzipped file...
Thanks again..