How to call a method on background thread - iphone

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..

Related

Updating Interface from Result of NSURL Request

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.

How to prevent AVAudioPlayer from playing before a view is displayed

Hi there I have an a UIView named HomeViewController that use UINavigationController. On a click of a button, I push another view using the following code:
gameView = [[GameViewControler alloc] init];
[[self navigationController] pushViewController:gameView animated:YES];
[gameView release];
In GameViewController's viewDidLoad, I use the following code to play two audio file one after another:
//handle sound stuff
if([[GameStore defaultStore] currentIndex] == 0)
{
if(![audioPlayer isPlaying])
{
NSString *findPath = [[NSBundle mainBundle] pathForResource:#"findtheword" ofType:#"aiff"];
if(findPath)
{
NSURL *findURL = [NSURL fileURLWithPath:findPath];
NSError *findError;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:findURL error:&findError];
if(audioPlayer == nil)
{
NSLog([findError description]);
}
else{
[audioPlayer play];
}
}
}
}
while ([audioPlayer isPlaying]) {
//do nothing and wait so the sound is not overlap
}
if(![audioPlayer isPlaying] || ([audioPlayer isPlaying] && override)){
Word *currentWord = [[GameStore defaultStore] currentWord];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:[currentWord fileName]
ofType:[currentWord fileType]];
// If this file is actually in the bundle...
if (soundPath) {
// Create a file URL with this path
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
if (audioPlayer == nil)
NSLog([error description]);
else
[audioPlayer play];
}
[FlurryAnalytics logEvent:#"GameViewController - playSound"];
}
else{
[FlurryAnalytics logEvent:#"GameViewController - playSound is still playing"];
}
I can't figure out why the first audio file started to play while the screen is still on HomeViewController even though it is being called from GameViewController's viewDidLoad.
If I remove the following code:
while ([audioPlayer isPlaying]) {
//do nothing and wait so the sound is not overlap
}
The first audio file started to play when GameViewController is loaded but the 2nd audio file overlap.
Thank you very much.
Does the audio file start to play after you click some button or table view cell in HomeViewController? As in, it starts to play while the GameViewController's view is being animated from right to left? If so, then that's because -viewDidLoad is called before the view is actually presented. What you need to do is instead place your view logic in -viewDidAppear:. That will only be called when the view is finished animating and is completely visible.
You could move the code from viewDidLoad to viewDidAppear:.

Touchesbegan always fire when using touchesmoved?

I'm new to iPhone programing.
I'm trying to do a simple window with a cat doing two sounds. When you click at the cat icon it should do "miaau", and when wou drag over window (stroke) it should make "mrrrr".
It works, but always when I try to make cat mrrrrr function TouchesBegan fires and cat also do "miaaau".
What to do to make the interface recognize I want only stroke cat, not to touch it for doing the first option, "miaau"?
I suggest to add a NSTimer to touchesBegan method with small time interval (say 0.1 sec):
BOOL tap_event = NO; //ivar declared in header
-(void) touchesBegan:... {
tap_event = YES;
[NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: #selector(checkTap:) userInfo: nil repeats: NO];
}
-(void) checkTap:(NSTimer*) t {
if( tap_event ) //miauu here
tap_event = NO;
}
-(void) touchesMoved:... {
tap_event = NO;
//mrrrr here
}
Or as an option check docs for UIGestureRecognizers
This may help you
When does a touchesBegan become a touchesMoved?
touchesBegin & touchesMove Xcode Obj C Question
Max' solution is correct. It will definitely work. I am having an alternative, see this approach.
Make your player object in an .h file then allocate it in viewDidLoad and release in dealloc.
-(void) touchesBegan:... {
//Play the miauu sound.
}
-(void) touchesMoved:... {
[self.yourPlayer stop];
//Play mrrrr.
}
Edit
.h file,
AVAudioPlayer *avPlayer1;
AVAudioPlayer *avPlayer2;
.m file
-(void) touchesBegan:... {
NSString *path = [[NSBundle mainBundle] pathForResource:#"cat" ofType:#"wav"];
avPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[avPlayer1 play];
}
-(void) touchesMoved:... {
[avPlayer1 stop];
NSString *path = [[NSBundle mainBundle] pathForResource:#"cat" ofType:#"wav"];
avPlayer2 = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[avPlayer2 play];
}
-(void)dealloc
{
[avPlayer1 release];
[avPlayer2 release];
[super dealloc];
}

iPhone - tracking back button

I have a tableView which lists the contents of my document directory. I have some zip files in that. If I touch a file in the tableView, the corresponding zip file is unzipped and extracted in a temporary directory(newFilePath in my case). The contents unzipped is listed in the next tableView. When I touch the back button, the contents in the directory is listed again.
For example, consider that I have four zip files in my document directory.
songs.zip, videos.zip, files.zip, calculation.zip
When I run the application, all the four files are listed in the tableView. When I touch songs.zip, this file is extracted in the newFilePath and its contents are pushed to the next tableView. When I touch back, the previous tableView, i.e, the four files in the document directory are listed again. Everything works perfect.
The problem is, the extracted files in the newFilePath remains there itself. They occupy the memory unnecessarily. I want them to be removed from that path when I touch the back button, i.e, I want to make newFilePath empty when the back button is touched.
I tried for it. But, no use. I tried removeItemAtPath: method in viewWillAppear: and also in viewWillDisappear:. But it didnt work in both the cases.
Is there any other method to track the action of the back button? I want an event to take place when the back button is touched. So please help me by sharing your ideas. Here is my code for your verification.
This is my didSelectRowAtIndexPath:
NSString *filePath = //filePath
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSLog(#"File exists at path: %#", filePath);
} else {
NSLog(#"File does not exists at path: %#", filePath);
}
ZipArchive *zip = [[ZipArchive alloc] init];
NSString *newFilePath = //newFilePath
[[NSFileManager defaultManager] createDirectoryAtPath:newFilePath withIntermediateDirectories:NO attributes:nil error:nil];
BOOL result = NO;
if([zip UnzipOpenFile:filePath]) {
//zip file is there
if ([zip UnzipFileTo:newFilePath overWrite:YES]) {
//unzipped successfully
NSLog(#"Archive unzip Success");
result= YES;
} else {
NSLog(#"Failure To Extract Archive, maybe password?");
}
} else {
NSLog(#"Failure To Open Archive");
}
iDataTravellerAppDelegate *AppDelegate = (iDataTravellerAppDelegate *)[[UIApplication sharedApplication] delegate];
//Prepare to tableview.
MyFilesList *myFilesList = [[MyFilesList alloc] initWithNibName:#"MyFilesList" bundle:[NSBundle mainBundle]];
//Increment the Current View
myFilesList.CurrentLevel += 1;
viewPushed = YES;
//Push the new table view on the stack
myFilesList.directoryContent = [AppDelegate getTemporaryDirectoryItemList:newFilePath];
[myFilesList setTitle:detailedViewController.strName];
[self.navigationController pushViewController:myFilesList animated:YES];
[myFilesList release];
Thank you for your answers.
Oh ya, thats quite simple:
in LoadView,
self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]
initWithTitle:#"Back"
style:UIBarButtonItemStylePlain
target:self
action:#selector(backButtonHit)];
-(void)backButtonHit
{
// removeItemAtPath: newFilepath stuff here
[self.navigationController popViewControllerAnimated:YES];
}

Iphone NSTimer Issue

Hi I am new to objective c. I am trying to make an app for iphone. I have a button on my view, and the click on which the function playSound is called. This is working properly. It does plays the sound that i want it to.
Now the problem is with the timer. I want the timer to start on the click on the same button, and the timer value will be displayed in a label. I am not very clear with the NSTimer itself either yet. I guess i am doing something wrong here. Can anyone help me with this.
-(IBAction)playSound { //:(int)reps
NSString *path = [[NSBundle mainBundle] pathForResource:#"chicken" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
theAudio.delegate = self;
[theAudio play];
[self startTimer];
}
- (void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(targetMethod) userInfo:nil repeats:YES];
labelA.text = [NSString stringWithFormat:#"%d", timer];
}
Using the code above, when i click on the button, it plays the sound, and then my app closes.
Thanks
Zeeshan
This line:
labelA.text = [NSString stringWithFormat:#"%d", timer];
makes absolutely no sense. The timer will call the method you specify as the selector in scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: when it fires so you have to implement that method and update your label there. The first line of startTimer is almost correct, but the selector must include a colon (because it denotes a method that takes one parameter):
- (void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
}
Note that I named the selector timerFired: so we have to implement that method. If you want the timer to increment a counter, you will have to do that in this method, too:
- (void)timerFired:(NSTimer *)timer {
static int timerCounter = 0;
timerCounter++;
labelA.text = [NSString stringWithFormat:#"%d", timerCounter];
}
Don't forget to invalidate the timer later when you no longer need it.