Button press doesn't work during parsing - objective-c

I cannot make back button (previous page in navigation) work during html parsing process. What I am trying to do is parsing an html and get a pdf file on viewDidAppear. During that time I want back button to be enabled because parsing can take long time. So users can decide to leave parsing process.
here is my code:
-(void)viewDidAppear:(BOOL)animated
{
[self performSelector:#selector(getPDFUrl) withObject:nil afterDelay:0];
}
-(void) getPDFUrl
{
NSURL *programURL = [NSURL URLWithString:#"http://www.example.com/somepdf/"];
NSData *programHtmlData;
#try
{
programHtmlData = [NSData dataWithContentsOfURL:programURL];
}
#catch(NSException* ex)
{}
// 2
TFHpple *programHTMLParser = [TFHpple hppleWithHTMLData:programHtmlData];
NSString *studiosXpathQueryString =
#"//div[#class='ultra_wrapper']/div[#class='container columns extra_pad boxed_lay centered']/div[#id='prk_ajax_container']/div[#id='centered_block']/div[#id='main_block']/div[#id='content']/div[#id='main']/div[#class='twelve columns sidebarized']/div[#class='prk_no_composer']/p/a";
NSArray *programNodes = [programHTMLParser searchWithXPathQuery:studiosXpathQueryString];
NSMutableArray *activities = [[NSMutableArray alloc] init];
Tutorial *tutorial;
if (programNodes.count > 0) {
for (TFHppleElement *element in programNodes)
{
#try
{
tutorial = [[Tutorial alloc] init];
tutorial.url = [element objectForKey:#"href"];
}
#catch(NSException* ex)
{
}
}
NSURL *targetURL = [NSURL URLWithString:tutorial.url];
webView.scalesPageToFit=YES;
NSURLRequest *request = [NSURLRequest requestWithURL:targetURL];
[webView loadRequest:request];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"warning"
message:#"warning!"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}

[self performSelector:#selector(getPDFUrl) withObject:nil afterDelay:0];
will execute on main thread. Therefore Blocking your UI.
You should use
[self performSelectorInBackground:#selector(getPDFUrl) withObject:nil];
Or you can use NSOperationQueue or just NSOperation or more simple way NSBlockOperation.
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"dasds");
}];
[op start];

Related

AVAudioPlayer breaking video capture

In one of the views of my app there's a button. When pressed it is supposed to begin taking a video, trigger a sound file to start, and hide itself from view while unhiding another button. The second button is supposed to stop the video recording and make it save. Here's the code I have for the video recording, which initially worked with no problems:
in viewDidLoad:
finishButton.hidden = TRUE;
session = [[AVCaptureSession alloc] init];
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
NSError *error;
AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameraWithPosition:AVCaptureDevicePositionFront] error:&error];
if (videoInput)
{
[session addInput:videoInput];
}
AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
NSError *audioError = nil;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&audioError];
if (audioInput)
{
[session addInput:audioInput];
}
Float64 TotalSeconds = 35; //Total seconds
int32_t preferredTimeScale = 30; //Frames per second
CMTime maxDuration = CMTimeMakeWithSeconds(TotalSeconds, preferredTimeScale);
movieFileOutput.maxRecordedDuration = maxDuration;
movieFileOutput.minFreeDiskSpaceLimit = 1024 * 1024;
if ([session canAddOutput:movieFileOutput])
[session addOutput:movieFileOutput];
[session setSessionPreset:AVCaptureSessionPresetMedium];
if ([session canSetSessionPreset:AVCaptureSessionPreset640x480]) //Check size based configs are supported before setting them
[session setSessionPreset:AVCaptureSessionPreset640x480];
[self cameraSetOutputProperties];
[session startRunning];
and for the button:
-(IBAction)start:(id)sender
{
startButton.hidden = TRUE;
finishButton.hidden = FALSE;
//Create temporary URL to record to
NSString *outputPath = [[NSString alloc] initWithFormat:#"%#%#", NSTemporaryDirectory(), #"output.mov"];
self.outputURL = [[NSURL alloc] initFileURLWithPath:outputPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:outputPath])
{
NSError *error;
if ([fileManager removeItemAtPath:outputPath error:&error] == NO)
{
//Error - handle if required
}
}
//Start recording
[movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
finally, under the last button:
[movieFileOutput stopRecording];
and here's the code to save the video:
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error
{
NSLog(#"didFinishRecordingToOutputFileAtURL - enter");
BOOL RecordedSuccessfully = YES;
if ([error code] != noErr)
{
// A problem occurred: Find out if the recording was successful.
id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value)
{
RecordedSuccessfully = [value boolValue];
}
}
if (RecordedSuccessfully)
{
//----- RECORDED SUCESSFULLY -----
NSLog(#"didFinishRecordingToOutputFileAtURL - success");
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL])
{
[library writeVideoAtPathToSavedPhotosAlbum:outputURL
completionBlock:^(NSURL *assetURL, NSError *error)
{
if (error)
{
}
}];
}
}
}
All of this was working just fine. Then I added a few lines so that a song file would play when the start button was pressed.
in viewDidLoad:
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Song.aiff", [[NSBundle mainBundle] resourcePath]]];
NSError *audioFileError;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&audioFileError];
player.numberOfLoops = 0;
[self.player prepareToPlay];
and under the start button:
if (player == nil)
NSLog(#"Audio file could not be played");
else
[player play];
Now when the start button is pressed the song plays with no problems, but the video capture is messed up. Before adding the AVAudioPlayer stuff I would get the "didFinishRecordingToOutputFileAtURL - enter" and "didFinishRecordingToOutputFileAtURL - success" logs when I pressed the finish button, and now I get the first log as soon as I press the start button, nothing happens when I press the finish button, and no video is recorded. If I comment out the lines that make the song play then the video capture works just fine again. Any ideas what's going on here?
- (void)setupAudioSession
{
static BOOL audioSessionSetup = NO;
if (audioSessionSetup)
{
return;
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 1;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(doSetProperty), &doSetProperty);
[[AVAudioSession sharedInstance] setActive: YES error: nil];
audioSessionSetup = YES;
}
- (void)playAudio
{
[self setupAudioSession];
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"btnClick" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[fileURL release];
self.audioPlayer = newPlayer;
[newPlayer release];
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
audioPlayer.volume=1.0;
[audioPlayer play];
}
NOTE: Add the framework: AudioToolbox.framework.
#import <AudioToolbox/AudioServices.h>

Method gets called two times

I am doing a NSURLConnection that downloads a file only if there is a new one (checked with the last-modified date). But to accomplish this I am using two methods with two different NSURLRequests and NSURLConnection. Well, they do the same.
- (IBAction)uppdatera:(id)sender
{
checkHeaders = YES;
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.forellgatan.se/site/ftp_files/Kapareskolan.zip"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection)
{
self.receivedData = [[NSMutableData data] retain];
}
else
{
UIAlertView *connectFailMessage = [[UIAlertView alloc] initWithTitle:#"Ingen internetanslutning! 1" message:#"Anslut dig till internet för att ladda ner!" delegate: self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[connectFailMessage show];
[connectFailMessage release];
}
}
- (void)downloadNewFile
{
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.forellgatan.se/site/ftp_files/Kapareskolan.zip"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0];
NSURLConnection *theConnection2 = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection2)
{
self.receivedData = [[NSMutableData data] retain];
}
else
{
UIAlertView *connectFailMessage = [[UIAlertView alloc] initWithTitle:#"Ingen internetanslutning! 2" message:#"Anslut dig till internet för att ladda ner!" delegate: self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[connectFailMessage show];
[connectFailMessage release];
}
checkHeaders = NO;
self.progressView.hidden = NO;
}
It goes through the didReceiveResponse method:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (checkHeaders == YES)
{
NSHTTPURLResponse *test = (NSHTTPURLResponse *)response;
if ([test respondsToSelector:#selector(allHeaderFields)])
{
NSDictionary *metaData = [test allHeaderFields];
NSString *lastModifiedString = [metaData objectForKey:#"Last-Modified"];
NSString *savedString = [[NSUserDefaults standardUserDefaults] stringForKey:#"LastModified"];
if (![lastModifiedString isEqualToString:savedString])
{
[self downloadNewFile];
}
else if ([lastModifiedString isEqualToString:savedString])
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Ingen uppdatering tillgänglig" message:#"Det finns ingen uppdatering att hämta just nu." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
[alert release];
}
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
[standardUserDefaults setObject:lastModifiedString forKey:#"LastModified"];
[standardUserDefaults synchronize];
}
}
[self.receivedData setLength:0];
self.fileSize = [[NSNumber numberWithLong: [response expectedContentLength]] retain];
}
Last the connectionDidFinishLaunching method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (checkHeaders == NO)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
[self.receivedData writeToFile:[basePath stringByAppendingPathComponent:#"Kapareskolan.zip"] atomically:YES];
[self unzipDownloadedFile];
self.progressView.hidden = YES;
NSLog(#"Finished...");
}
[connection cancel];
}
I know the didFinishLaunching method gets called twice, but I want to know how I could get so the method doesn't get called twice if there is an update?
I know it's a lot asked and much code here but just give me a hint and I'll be very thankful.
If you're done with the first connection object in the didReceiveResponse method you should cancel it then. Otherwise it's going to make it to the connectionDidFinishLoading method.
I think this is what you want:
if (![lastModifiedString isEqualToString:savedString])
{
[connection cancel];
[self downloadNewFile];
}
Also It looks like you're setting checkHeaders to NO after you start the second request which could cause a race condition.
According to the programming guide, a connection "can be canceled any time before the delegate receives a connectionDidFinishLoading: or connection:didFailWithError: message by sending the connection a cancel message".
so why not try moving
[connection cancel];
from the connectionDidFinishLoading method to just after your if-else block in the didReceiveResponse delegate method? You want to cancel the "checkHeaders==YES" connection in either case; either you're about to kick off a new connection, or you already know all you need to know about the current connection.
UPDATED as requested:
if (![lastModifiedString isEqualToString:savedString]) {
[self downloadNewFile];
} else { // you've already implicitly checked for equality above
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Ingen uppdatering tillgänglig" message:#"Det finns ingen uppdatering att hämta just nu." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
[alert release];
}
// you've used the connection for everything that you need, so cancel it in either case
[connection cancel];
as downloadNewFile kicks off its NSURLConnection asynchronously, this should be okay in the event that the two strings are equal. Slightly safer would be to move the cancel method call to just prior to the if-else check.

Put UIAlertView above UIActivityIndicatorView

I want to see in a moment my UIAlert above my UIActivityIndicatorView, i have this code:
- (void)showWithLabelDeterminate{
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
// Set determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.delegate = self;
HUD.labelText = #"Loading";
// myProgressTask uses the HUD instance to update progress
[HUD showWhileExecuting:#selector(myProgressTask) onTarget:self withObject:nil animated:YES];
}
- (void)myProgressTask
{
float progress = 0.0f;
while (progress < 0.99f)
{
progress += 0.01f;
HUD.progress = progress;
usleep(50000);
if(progress > 0.02f & progress < 0.05f)
{
NSString * string = [NSString stringWithFormat:#"http://localhost:8080/......"];
NSURL *url = [NSURL URLWithString:string];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(!urlData)
{
[self simpleAlert];
}
else
{
datos = [[NSMutableString alloc]initWithData:urlData encoding:NSISOLatin1StringEncoding];
}
}
}
}
-(void)simpleAlert
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Dont connect!!" delegate:self cancelButtonTitle:#"Refresh" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
The problem is that i dont see my UIAlertView above my UIActivityIndicatorView.....
myProgressTask is either executing on a background thread or is blocking the main thread with its calls to usleep.
If it's on a background thread then your use of UIAlertView is invalid because UIKit is, in the general case, usable from the main thread only. The quickest solution would be to use performSelectorOnMainThread:... to call simpleAlert.
If it's on the main thread then you're probably blocking UIKit from operating. Obvious comments would follow about using a non-blocking NSURLConnection and using NSTimer or performSelector:withObject:afterDelay: to schedule things to happen after a pause.

Singleton Property causing App crash

I have a singelton class which is used to pull and store all data from a Web Service. The class has an NSArray and an NSDictionary which stores the data. I then want to use this data to populate tableviews in other views.
In one of my views, within the View Did Load method, I tell the singleton to retrieve the data from the web service and store it (it successfully retrieves the data, I logged it). I then try to access the data like so:
[[ClubData initClubData] memberData];
If I try to use that to populate the table view, it crashes and keeps referencing some kind of view. One time it referenced CALayer, another time a WrapperView ??? Am I not doing something right
My Singleton:
#import "ClubData.h"
#import "JSON.h";
#implementation ClubData
#synthesize conn, response, url, api_calls, memberData, beerData, call;
static ClubData *globalClubData = nil;
#pragma mark -
#pragma mark Instance Methods
- (void)getBeerData {
}
- (void)getSingleBeer:(NSInteger *)beerID {
}
- (void)getClubData {
//check for existing data
if (memberData != nil)
return;
//init
memberData = [[NSArray alloc] init];
//create request
call = #"memberlist";
[self initRequest:#"memberlist"];
}
- (void)getMemberData:(NSInteger *)memberID {
}
- (void)parseData:(NSString *)json {
//parse based on call type
if (call == #"memberlist") {
memberData = [[NSDictionary alloc] init];
memberData = [json JSONValue];
}
//reset call
[call release];
call = nil;
}
#pragma mark -
#pragma mark Connection & Delegate
- (void)initRequest:(NSString *)type {
//build url
NSMutableString *rURL = [[NSMutableString alloc] initWithString:url];
[rURL appendString:[api_calls objectForKey:type]];
NSURL *tempURL = [[NSURL alloc] initWithString:rURL];
[rURL release];
//init request & create connection
NSURLRequest *request = [[[NSURLRequest alloc] initWithURL:tempURL] autorelease];
conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[tempURL release];
//init response
response = [[NSMutableData alloc] init];
}
//receive data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[response appendData:data];
}
//connection complete
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//release conn
[conn release];
//parse JSON
NSString *json = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
[response release];
//parse
[self parseData:json];
[json release];
}
//connection failed
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Error"
message:#"Unable to connect to network. Network required to load data."
delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
[alert release];
}
#pragma mark -
#pragma mark Singleton Control
+ (ClubData *)initClubData {
if (globalClubData == nil) {
#synchronized(self) {
globalClubData = [[super allocWithZone:NULL] init];
}
}
return globalClubData;
}
- (id)init {
if (self = [super init]) {
//set url
url = [[[NSString alloc] initWithString:#"https://myurl.com/path/to/script.php?action=get_app_data"] retain];
//possible calls
NSArray *keys = [[NSArray alloc] initWithObjects:#"beerlist", #"singlebeer", #"memberlist", nil];
NSArray *objs = [[NSArray alloc] initWithObjects:#"&subaction=beerlist", #"&subaction=singlebeer&beerID=", #"&subaction=memberlist", nil];
api_calls = [[[NSDictionary alloc] initWithObjects:objs forKeys:keys] retain];
[keys release];
[objs release];
}
return self;
}
#pragma mark -
#pragma mark Lifecycle
- (void)dealloc {
[conn release];
[response release];
[url release];
[call release];
[api_calls release];
[super dealloc];
}
#end
api_calls = [[[NSDictionary alloc] initWithObjects:objs forKeys:keys] retain];
do not retain it (you are already an owner of that object).
As for the crash - post the stack trace.
Edit: Probably found:
if (call == #"memberlist") {
memberData = [[NSDictionary alloc] init]; //remove this (it is redundant and causes leak)
memberData = [json JSONValue]; //retain this
}

What is asynchronous image downloading and how can I download too many images?

I have too many images to be download from net into iPhone? How can I build an application using asynchronous image downloading?.
Most common and simple way is to use NSURLConnection with asynchronous request. Create connection with request set delegate, and it starts load data in background calling delegate methods when receive next chunk of data, finish load or fail. when first object is loaded, start next and so on.
Here is slightly simplified working code:
- (id)init...{
//...
requestData = [[NSMutableData alloc] initWithCapacity:1000000];
myImages = [[NSMutableArray alloc] initWithCapacity:100];
myImagesAddresses = [[NSMutableArray alloc] initWithCapacity:100];
[myImagesAddresses addObject:#"http://mysite.com/image1"];
[myImagesAddresses addObject:#"http://mysite.com/image2"];
//...
[self loadNextImage];
//...
}
-(void)loadNextImage{
if ([myImagesAddresses count]){
NSURL * imageURL = [NSURL URLWithString:[myImagesAddresses lastObject]];
NSURLRequest * myRequest = [NSURLRequest requestWithURL:imageURL];
[[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
NSLog(#"start load URL:%#", imageURL);
}
else{
NSLog(#"No more images to load");
// all images are ready to use!
}
}
// connection delegate methods
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data{
NSLog(#"more data...");
[requestData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)inConnection{
[myImages addObject:[UIImage imageWithData:[NSData dataWithData:requestData]]];
[inConnection release];
inConnection = nil;
NSLog(#"Image from:%# loaded",[myImagesAddresses lastObject]);
[myImagesAddresses removeLastObject];
[self loadNextImage];
}
- (void)connection:(NSURLConnection *) inConnection didFailWithError:(NSError *)error{
[inConnection release];
inConnection = nil;
NSLog(#"Image from:%# not loaded",[myImagesAddresses lastObject]);
[myImagesAddresses removeLastObject];
[self loadNextImage];
}