How can I fade out an AVAudioPlayer on a background thread? - objective-c

I have an audio file which needs to fade out while the user is scrolling a UIScrollView. However, any performSelector:withObject:afterDelay: method is blocked until the user has stopped scrolling. So I have tried to create some code to perform a fadeout on another thread:
- (void)fadeOut
{
[NSThread detachNewThreadSelector:#selector(fadeOutInBackground:) toTarget:self withObject:self.audioPlayer];
}
- (void)fadeOutInBackground:(AVAudioPlayer *)aPlayer
{
NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
[self performSelector:#selector(fadeVolumeDown:) withObject:aPlayer afterDelay:0.1];
[myPool release];
}
- (void)fadeVolumeDown:(AVAudioPlayer *)aPlayer
{
aPlayer.volume = aPlayer.volume - 0.1;
if (aPlayer.volume < 0.1) {
[aPlayer stop];
} else {
[self performSelector:#selector(fadeVolumeDown:) withObject:aPlayer afterDelay:0.1];
}
}
It gets as far as the performSelector, but no further because I guess it's trying to perform on a thread it has no access to. I can't even change it for performSelector:onThread:withObject:waitUntilDone: because there is no delay option.
Any ideas? Why have they made it so hard to just fade out a sound? moan
Thanks!

I resolved a similar issue by scheduling the selector in a different run loop mode than the default one. This way it is not interfering with the scrolling events. Using the NSRunLoopCommonModes worked for me:
[self performSelector:#selector(fadeVolumeDown:)
withObject:aPlayer
afterDelay:0.1
inModes:[NSArray arrayWithObject: NSRunLoopCommonModes]];

Inspired by the answer above
while (theAudio.volume > 0.000000)
[self fadeVolumeDown];
- (void) fadeVolumeDown{
theAudio.volume -=0.01;
}

Related

MBProgress hud not showing Labeltext

I wan't to show Progress indicator (MBprogress hud), here's the code i implemented.
[NSThread detachNewThreadSelector: #selector(showMe) toTarget:self withObject:NULL];
in show method , i have (tried to) displayed MBprogress Hud but it is not showing label text.
-(void)showMe
{
if(hudForBal) // hudForBal is my MBprogressHud's object
{
[hudForBal removeFromSuperview];
[hudForBal release];
hudForBal = nil;
}
hudForBal =[[MBProgressHUD alloc]init];
hudForBal.labelText =#"Please wait...";
hudForBal.delegate = Nil;
[self.view addSubview:hudForBal];
[hudForBal show:YES];
}
it is working but it is not showing label text .What am i doing wrong?
Thanks in advance!
There's no need to create a new thread to do this, in fact, making changes to the UI on any thread other than the main thread is undefined behavior. If you're already on the main thread when you would be calling this method, then all you have to do is perform the selector as you normally would, without sending it to a different thread.
However, if you're already on a background thread when you would be performing this selector, and you want to update the UI, you can use dispatch_async() as a quick and easy way to move back to the main thread.
- (void)showMe {
dispatch_async(dispatch_get_main_queue(), ^{
if(hudForBal) {
[hudForBal removeFromSuperview];
[hudForBal release];
hudForBal = nil;
}
hudForBal =[[MBProgressHUD alloc]init];
hudForBal.labelText =#"Please wait...";
hudForBal.delegate = Nil;
[self.view addSubview:hudForBal];
[hudForBal show:YES];
});
}

Load an UIActivityIndicator over a UIImagePickerController in iOS

I am currently writing an iOS app and do not like the speed at which the UIImagePickerController disappears.
I call [self dismissModalViewControllerAnimated:YES]; the instant I am inside of - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info; And yet the imagePicker takes several moments to disappear, long enough on some photos that the app appears frozen to the user.
One solution I thought of was to just throw up a UIActivityIndicator infront of the UIImagePickerController but I have not figured out a way to accomplish this.
Thank you!
Edit: Any tips on how to save UIImages faster would help too I believe. Such as a way to do it asynchronously.
There is a great tutorial on doing this with grand central dispatch and code blocks by Ray Wenderlich :
http://www.raywenderlich.com/1888/how-to-create-a-simple-iphone-app-tutorial-part-33
You can also do this with a job queue and performSelector:onThread:withObject:waitUntilDone: if blocks are scary to you.
Basically, the idea is to do the least amount of real work on the main thread, because that's where the UI is drawn. Below is RW's solution, with the status part commented out. That's where you put an activity indicator.
- (IBAction)addPictureTapped:(id)sender {
if (self.picker == nil) {
// 1) Show status
//[SVProgressHUD showWithStatus:#"Loading picker..."];
// 2) Get a concurrent queue form the system
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 3) Load picker in background
dispatch_async(concurrentQueue, ^{
self.picker = [[UIImagePickerController alloc] init];
self.picker.delegate = self;
self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
self.picker.allowsEditing = NO;
// 4) Present picker in main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentModalViewController:picker animated:YES];
[SVProgressHUD dismiss];
});
});
} else {
[self.navigationController presentModalViewController:picker animated:YES];
}
}

UI not updating even though updates are called from main thread

I've read all related questions I could find but I'm still stuck, so I hope someone will spot my reasoning error.
I'm trying to periodically update some UIView. For simplicity, I've reduced the code to what's below. Summary: In viewDidLoad, I call a method on a new background thread. That method calls a method on the main thread which is supposed to update some UILabel. The code seems to work correctly: the background thread is not the main thread and the method calling the UILabel update is on the main thread. In code:
In viewDidLoad:
[self performSelectorInBackground:#selector(updateMeters) withObject:self];
This creates a new background thread. My method updateMeters (for simplicity) now looks like this:
if ([NSThread isMainThread]) { //this evaluates to FALSE, as it's supposed to
NSLog(#"Running on main, that's wrong!");
}
while (i < 10) {
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil waitUntilDone:NO];
//The code below yields the same result
// dispatch_async(dispatch_get_main_queue(), ^{
// [self updateUI];
// });
[NSThread sleepForTimeInterval: 1.05];
++i;
}
Finally, updateUI does just that:
if ([NSThread isMainThread]) { //Evaluates to TRUE; it's indeed on the main thread!
NSLog(#"main thread!");
} else {
NSLog(#"not main thread!");
}
NSLog(#"%f", someTimeDependentValue); //logs the value I want to update to the screen
label.text = [NSString stringWithFormat:#"%f", someTimeDependentValue]; //does not update
For all I know, this should work. But it doesn't, unfortunately... The commented out dispatch_async() yields the same result.
Most likely you have your format statement wrong.
label.text = [NSString stringWithFormat:#"%f", someTimeDependentValue];
Make sure that someTimeDependentValue is a float. If it is an int it will likely get formatted to 0.0000.
Here's a repo showing a working version of what you describe. Whatever is wrong is not related to the threading.
To expand on my comment, here's a scenario that might be best achieved using a NSTimer:
-(void)viewDidLoad
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:<number of seconds per tick> target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)timerTick:(id)sender
{
label.text = ...;
}
There is a more elaborate approach which I use extensively in my projects. And that is the concept of an engine.
I would have an engine that runs in the background using a timer. And at key moments, it would post a notification using NSNotificationCenter on the main thread using dispatch_async/dispatch_get_main_thread() and any one of your views can then subscribe and handle that notification by updating their UI.

How show activity-indicator when press button for upload next view or webview?

when i click on button which title is click here to enlarge then i want show activity indicator on the first view and remove when load this view.
but i go back then it show activity indicator which is shown in this view.
in first vie .m file i have use this code for action.
-(IBAction)btnSelected:(id)sender{
UIButton *button = (UIButton *)sender;
int whichButton = button.tag;
NSLog(#"Current TAG: %i", whichButton);
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[spinner setCenter:CGPointMake(160,124)];
[self.view addSubview:spinner];
[spinner startAnimating];
if(whichButton==1)
{
[spinner stopAnimating];
first=[[FirstImage alloc]init];
[self.navigationController pushViewController:first animated:YES];
[spinner hidesWhenStopped ];
}}
in above code i have button action in which i call next view. Now i want show/display activity indicator when view upload. In next view i have a image view in which a image i upload i have declare an activity indicator which also not working. How do that?
Toro's suggestion offers a great explanation and solution, but I just wanted to offer up another way of achieving this, as this is how I do it.
As Toro said,
- (void) someFunction
{
[activityIndicator startAnimation];
// do computations ....
[activityIndicator stopAnimation];
}
The above code will not work because you do not give the UI time to update when you include the activityIndicator in your currently running function. So what I and many others do is break it up into a separate thread like so:
- (void) yourMainFunction {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[NSThread detachNewThreadSelector:#selector(threadStartAnimating) toTarget:self withObject:nil];
//Your computations
[activityIndicator stopAnimating];
}
- (void) threadStartAnimating {
[activityIndicator startAnimating];
}
Good luck!
-Karoly
[self.navigationController pushViewController:first animated:YES];
Generally, when you push a view controller into navigation controller, it will invoke the -(void)viewWillAppear: and -(void)viewDidAppear: methods. You can add activity indicator view inside the viewWillAppear: and call startAnimation of indicator view. You CANNOT invoke startAnimation and stopAnimation at the same time. For example,
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// do somethings ....
[aIndicatorView stopAnimation];
}
Because the startAnimation and stopAnimation are under the same time, then no animation will show.
But if you invoke startAnimation in -(void)viewWillAppear: and invoke stopAnimation in another message, like followings.
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// do somethings...
}
- (void)viewDidAppear:(BOOL)animated
{
[aIndicatorView stopAnimation];
}
Because viewWillAppear: and viewDidAppear: are invoked with different event time, the activity indicator view will work well.
Or, you can do something like followings:
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// Let run loop has chances to animations, others events in run loop queue, and ... etc.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
// do somethings ....
[aIndicatorView stopAnimation];
}
The above example is a bad example, because it invokes two or more animations in the -runUntilDate:. But it will let the activity indicator view work.
Create a webview. Add a activity indicator to the webview. If you are loading a image via url into the webview then implement the webview delegate methods. Once the url is loaded then stopanimating the activity indicator.
Let me know which step you are not able to implement.

Refresh view with display and setNeedsDisplay not working

I am having problems updating my customView object during simulation. The window pops up after the simulation is done. I would like it to update itself during the simulation. For this I use setNeedsDisplay:YES and I have also tried display. None of this works for me however. Does anyone have an idea how I should get this working? As you can see below I have tried to create a new thread for the updating as well as using NSOperations. Grateful for help!
//Run simulation
for (int iteration=0; iteration<numberOfIterations; iteration++){
//NSInvocationOperation *update = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updatePopulation) object:nil];
//NSInvocationOperation *draw = [[NSInvocationOperation alloc] initWithTarget:view selector:#selector(redraw) object:nil];
//[draw addDependency:update];
//[queue addOperation:update];
//[queue addOperation:draw];
[NSThread sleepForTimeInterval:0.01]; //to make it easer to see..
[self updatePopulation];
//[view redraw];
[NSThread detachNewThreadSelector:#selector(redraw) toTarget:view withObject:nil];
//[self performSelector:#selector(updatePopulation) withObject:nil afterDelay:1];
//[view performSelector:#selector(redraw) withObject:nil afterDelay:1];
//Save segregation
if (iteration%(numberOfIterations/100) == 0) {
printf("hej\n");
}
}
in my viewer class:
- (void) redraw {
//[self setNeedsDisplay:YES];
[self display];
}
It looks like you are trying to do the painting on the worker thread.
This is not, as far as I am aware, supported.
To solve this, you need to move your simulation to a worker thread, and then use performSelectorOnMainThread: to invoke the redraw on the main thread. I find this article on threading in cocoa to be required reading when trying to implemented threaded cocoa apps.