I am downloading an mp3 using NSData dataWithContentsOfURL:url. This takes a while and while the file is downloading the application hangs. I want to handle well and ideal would like to show the download progress but can't find methods for this.
It is in a UIViewController and I have made a first attempt by putting in a UIActivityIndicatorView and start it spinning before I start the download, then stop it spinning after but nothing appears.
So my question really is please could someone tell me what the best way to handle this is? Thanks so much
Nothing will appear because your main thread is blocked doing the download, and the main thread is where UI updates occur.
You should use NSUrlConnection to download asynchronously and implement the delegate methods to start/stop your spinner.
Alternatively if you want to stick with NSData's dataWithContentsOfURL:url you should do this on a separate thread and update the spinner on the main thread before and after you call it.
You can achieve this while still using synchronous methods, but you need to give the run loop a chance to start animating the activity indicator before you start the download.
You can achieve this by using either performSelector:withObject:afterDelay: with delay 0 to put a run loop between your animation start and the download, or (worse style, more risky) you can directly invoke the run loop within your code.
Sample code:
- (void)loadPart1 {
activityIndicator = [[[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIA...StyleGray]
autorelease];
activityIndicator.frame = myFrame;
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[self performSelector:#selector(loadPart2) withObject:nil afterDelay:0];
}
- (void)loadPart2 {
[NSURLConnection sendSynchronousRequest:request returningResponse:&response
error:&error];
[activityIndicator stopAnimating];
}
More details here:
http://bynomial.com/blog/?p=15
(scroll down to Solution 1 or Solution 2).
Related
I am using the NSURLConnection to download a video file from the server, at the same time playing the video by passing different url link to the movieplayer.
The problem is some blocking of the UI. During downloading we are unable to interact with the UI, like player zoom, pause button are blocked.
Code is like this for connection:_
connection1=[[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]] delegate:self];
in same mithod we are calling the
[playerInstance Play]
Please can you explain me where the problem is.
I can't understand your problem exactly. But I think you doing the two works (downloadig, playing file) on a same thread, probably main thread. So this may happen. So try to run the two processes in a separate threads.
[self performSelectorOnMainThread:#selector(playfile:) withObject:nil waitUntilDone:NO];
[self performSelectorInBackground:#selector(downloadfile:) withObject:nil];
You should make function for NSUrl operation and if it is already made call this function in following way for asynchronous communication.
[self performSelector:#selector(method) withObject:nil afterDelay:1];
i have a big problem and i need your help. Here's what i need to accomplish:
The user select a row from a
TableView
A new view controller is pushed in
the NavigationController, and
displays only a "Loading" message
Meanwhile some data is read from an
XML file (via http)
When the data has been read, an
NSUConnection is used to load an
image from an URL (this URL is part
of the data)
While the image is still loading,
the other data is displayed on the
screen
The image has been downloaded and is
shown, completing the appearance of
the view
The big problem is that i can't use detachNewThreadSelector and NSURLConnection together!
So how can i make a workaround for this? How would you do this?
Thank you VERY much!
You can use following approach...(if you are using asynchronous request)
When your application comes in - (void)connectionDidFinishLoading:(NSURLConnection *)connection ... add a NSInvocationOperation object in NSOperationQueue (which you can handle at application level, by synthesizing it in appDelegate) ..
create NSInvocationOperation as follows..(in connectionDidFinishLoading)
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(parseIt) object:nil];
[appDelegate.operationQueue addOperation:operation];
[operation release];
-(void) parseIt
{
//ask for parsing stuff....what you have earlier wrote directly in connectionDidFinishLoading
}
Thanks,
I would use a NSTimer to solve the problem using detachNewThreadSelector and NSURLConnection together.
I have similar scenario where there is a downloading Progress UIViewController showing till the file getting complete, here is what i do:
I Draw a loading View contains a Activity Indicator for example.
I initialize a NSTimer to keep checking if the file is complete.
I call the method that contains the Download Logic.
[1]
-(void) vManageFileRequest
{
[self.oFilesManager vGetSingleFileRequest];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(vValidateFileRequest) userInfo:nil repeats:NO]];
}
[2]
[self performSelectorOnMainThread:#selector(vManageFileRequest) withObject:nil waitUntilDone:NO];
I load a huge-huge image with imageWithContentsOfFile:, so I have to set up an activityIndicator during the process.
Is there any way/any delegate callback I can use to be informed about the end of this loading process?
imageWithContentsOfFile is synchronous.
You could start an activity indicator, load your big image into memory in a background thread and then go back to the main thread and stop the indicator.
- (void)loadBigImage {
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(loadBigImageInBackground) withObject:nil];
}
- (void)loadBigImageInBackground {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *img = [UIImage imageWithContentsOfFile:#"..."];
[self performSelectorOnMainThread:#selector(bigImageLoaded:) withObject:img waitUntilDone:NO];
[pool release];
}
- (void)bigImageLoaded:(UIImage *)img {
[activityIndicator stopAnimating];
// do stuff
}
Short answer: Nope. sorry!
Long answer :
You could open the file in a background process (an NSOperation?) bit by bit using C style methods i.e. fopen, fread etc) and fire notifications back to the main thread during the load. Then create the image and fire a notification that the image is ready?
If you want to have a delegate & be informed of the progress of the load, you can use an NSURLConnection instead of the synchronous imageWithContentsOfFile.
There's an example of this in the Apple URL Loading System Programming Guide
Your NSURLConnection delegate didReceiveData: method could append the incoming data to an NSData object, then you would use UIImage imageWithData: to create them image once everything's downloaded.
This gives you the most flexibility/control over monitoring the progress of the load; although if all you're trying to do is avoid hanging the UI while the image downloads, simply using imageWithContentsOfFile in a background thread may be easier.
I had the UIActivityIndicatorView working fine in simulator and other 3.0 devices in my app. But I found out that it was not spinning (or showing) in the new iphone 4. Basically I need to show the activity indicator when a button is clicked and hide it when the button click event is complete. I was using the approach below.
[NSThread detachNewThreadSelector: #selector(spinBegin) toTarget:self withObject:nil];
from this link. As mentioned, it correctly spins the activity indicator on all except 4.*.. not sure why. To get around this, I also followed another approach something like (from developer.apple.com)
`
(IBAction)syncOnThreadAction:(id)sender
{
[self willStartJob];
[self performSelectorInBackground:
#selector(inThreadStartDoJob:)
withObject:theJobToDo
];
}
(void)inThreadStartDoJob:(id)theJobToDo
{
NSAutoreleasePool * pool;
NSString * status;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
status = [... do long running job specified by theJobToDo ...]
[self performSelectorOnMainThread:
#selector(didStopJobWithStatus:)
withObject:status
waitUntilDone:NO
];
[pool drain];
}
`
The problem with this was that, it is showing the acitivityVIewIndicator spinning correctly (at least on the simulator) but after it stops, the built in activity indicator in the top bar (where it shows the battery% etc) is still spinning.
I'm new to objective C. I have finished my app completely but for this silly thing. I realize there is no way to display UIActivityView without starting another thread. and finally, just to rant, I don't understand why they have to make it so complicated. I mean they knew it was going to have this problem, why not provide a sample code everyone can use rather than deriving their own solutions.
Finally, can anyone please provide me with a direction or some sample code. I would really appreciate it. I have been searching for a few hours now and have not found anything really that works!
Why are you starting/stopping the indicator on a separate thread? Any methods you send to your UIActivityIndicatorView must be sent on the main (UI) thread.
Any events sent by a button pressed will automatically be run on the main thread. If you're using background threads to complete the process, you could do something like:
- (IBAction)buttonPressed:(id)sender {
// This runs on the main thread
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(inThreadStartDoJob:) withObject:theJobToDo];
}
- (void)inThreadStartDoJob:(id)theJobToDo {
// Set up autorelease pool
...
// Run your long-running action
...
// Stop the spinner. Since we're in a background thread,
// we need to push this to the UI Thread
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
Edit: As for the activity indicator in the top bar (where the battery is), doesn't this automatically start/stop based on network activity?
I'm trying to download the picture from URL and use a activity indicator animating to present the fact that the file is downloading. However, it is not working as my indicator isn't animating when I call this download function, can somebody tell me why?
-(void)download{
[indicator startAnimating];
NSString *downloadPath=#"http://www.xyz.com/path/pic.jpg;
NSData *downloadData=[NSData dataWithContentsOfURL:[ NSURL URLWithString:downloadPath]];
if(downloadData){
//do something
[indicator stopAnimating];
}
else{
//do something
[indicator stopAnimating];
}
}
You need to put [indicator startAnimating] and [indicator stopAnimating]; in separate methods. I believe the animation does not kick in until the method reaches its end. So if you separate this into several methods this should work
One method that starts your animation
One method that downloads the file.
One method that stops the animation.
Another option is threading to acomplish this. More information here
The animation is performed in the event loop, which is in the same thread as your code. That is, the animation won't start while your code is executing.
Instead you either need to forget about the animation, switch to using the asynchronous download methods or perform the download in a separate thread. I'd recommend the async option.