UIButton is not refreshing inside thread - objective-c

I have a method inside a thread. Before tthe calling of the thread, I disabled a button, and I can see the button eith the alpha changed.
Inside the thread, I'm calling the async method AVAssetExportSession and then, I enable the button.
The button works perfectly but the alpha is not changing every time immediately. Sometimes is after 0.1 seconds, sometimes is after 4 seconds.
I tried to execute the enable in the main thread, but I have the same problem.
Any clue about it?
Thanx
UPDATE: I will try to write some code.
[NSThread detachNewThreadSelector:#selector(importaVideo:) toTarget:self withObject:arrayParam];
And this is the method called:
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:[Utilidades getQualityExport]];
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
exportSession.outputURL = [NSURL fileURLWithPath:[Utilidades devuelvePath:filenameVideo]];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
self.buttonSettings.enabled = YES;
}];
I know that this is not the complete code, but the other part is a little complex and is not affecting to the final result.

Related

iOS7 SpriteKit how to wait for an animation to complete before resuming method execution?

Is there a way to make a method wait for a SpriteKit action initiated within the method to complete before continuing code execution? Here's what I have so far, but it just hangs at the wait loop.
__block BOOL wait = YES;
SKAction* move = [SKAction moveTo:destination duration:realMoveDuration];
SKAction* sequence = [SKAction sequence:#[[SKAction waitForDuration:0.07],move,[SKAction waitForDuration:0.07] ]];
[spellEffect runAction:sequence completion:^{
[spellEffect removeFromParent];
wait = NO;
}];
DLog(#"Waiting");
while (wait) {
}
DLog(#"Done waiting");
I think you have a misunderstanding about the execution of blocks, apple has great docs: here. Your while loop will run infinitely, and even if the block did stop it, using that type of system to block your main thread will likely get your app shot down during review.
You should continue within the block. If for instance, you wanted to continue with a method called continueAfterAnimationIsDone, you could do
// Runs first
[spellEffect runAction:sequence completion:^{
[spellEffect removeFromParent];
wait = NO;
// Done
// Runs third
[self continueAfterAnimationIsDone]; // this method won't start until animations are complete.
}];
// Runs second
Note that when you are declaring the block, the code within it is captured and animations begin on a background thread. The rest of your code continues, so you're waiting loop will start. Once animations are completed this block executes, but it doesn't have a way to properly notify your while loop and so the loop never ends. Let me know if you have further questions, blocks can be strange.

Check if NSAnimationContext runAnimationGroup cancelled or succeeded

I'm animating a view (by revealing it) after which I need to post a notification (once the animation completes). However the way the app's designed, there's another notification sent out when the view is hidden (via animation). So essentially I have a 'showView' and a 'hideView' method. Each do something like so:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration: 0.25];
[[myView animator] setAlphaValue: 0];
} completionHandler:^{
// post notification now
}];
The problem is that in another thread in the background, I have a periodic timer which performs a 'sanity check' to see if the view needs to be hidden if it's not needed. It's an aggressive approach but necessary as many components of the app respond to different events and scenarios. Due to this I at times get a 'race condition' where the user has clicked to 'showView' but at the same time one of our threads feel the view should be immediately hidden.
When both post a notification on the main thread, the app hangs indefinitely in a SpinLock. I could completely avoid this situation if I was able to figure out if the animation block above was 'cancelled' (i.e. another animation block executed on the same view). In such situations I would not post the notification, which would then not force a check.
Long story short: I need to be able to check if the 'completionHandler' was called after animation successfully ended or if it was cancelled. I know in iOS this is possible but I can't find any way to do this in OS X. Please help.
I encountered a similar problem. I solved it by using a currentAnimationUUID instance variable.
NSUUID *animationUUID = [[NSUUID alloc] init];
self.currentAnimationUUID = animationUUID;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
// set properties to animate
} completionHandler:^{
BOOL isCancelled = ![self.currentAnimationUUID isEqual:animationUUID];
if (isCancelled) {
return;
}
self.currentAnimationUUID = nil;
// do stuff
}];
Swift version:
let animationUUID = UUID()
currentAnimationUUID = animationUUID
NSAnimationContext.runAnimationGroup({ context in
// set properties to animate
}, completionHandler: {
let isCancelled = self.currentAnimationUUID != animationUUID
guard !isCancelled else {
return
}
self.currentAnimationUUID = nil
// do stuff
})

Core Animation + Core Audio + sleep not happening in the order expected

Below is some code I'm working with.
What I want/expect:
Image fades in; sound plays; delay; image fades out.
What happens
Delay, sound plays, image fades in and then fades out immediately after
Any help much appreciated.
ViewController.m
[self fadein];
NSString *tempFN;
SystemSoundID soundID;
tempFN = [[NSString alloc] initWithFormat:#"%#.caf",
[myGlobals.gTitleArray objectAtIndex:myGlobals.gQuoteCountForLoop]];
NSString *soundFilePath = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], tempFN];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
sizeof (sessionCategory),
&sessionCategory);
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundFileURL, &soundID);
AudioServicesPlaySystemSound(soundID);
// Now fade-out the image after a few seconds delay
sleep(3);
[self fadeout];
-(IBAction)fadein
{
[UIImageView beginAnimations:NULL context:nil];
[UIImageView setAnimationDuration:2];
[faceImage setAlpha:0.3];
[UIImageView commitAnimations];
}
-(IBAction)fadeout
{
[UIImageView beginAnimations:NULL context:nil];
[UIImageView setAnimationDuration:1];
[faceImage setAlpha:0];
[UIImageView commitAnimations];
}
I believe the problem is when you use "sleep(5)". Using the sleep() function blocks the main thread so nothing can be done for that time period. It is exceedingly rare to use the sleep() function in Objective-C except maybe in some command-line utilities.
I recommend that you use an NSTimer instead of the sleep() function.
Hope this helps.
//Edit
Also if you really must use sleep(), you should instead use the sleepForTimeInterval: function of the NSThread class and get the rest of your code running on a secondary thread. Once again, I really don't think you should use the sleep() function or any related functions.
As the other poster said, sleep is the problem. Sleep freezes the main thread. Forget that sleep exists. Never use it. The only time sleep is EVER valid is on a secondary thread, when you want that thread to stop running for a period, and then only if you really really know what you are doing with concurrency (a very advanced topic)
What you should do is rewrite your animation code to use animateWithDuration:animations:completion:
Provide a completion block that invokes the next animation using a nested call to animateWithDuration:animations:completion:
Each animation will run to the end, and then invoke the next one.
BTW, you should look at AVAudioPlayer. It is more powerful than AudioServices, and easier to use. You just create an AVAudioPlayer and tell it to play.
If you want a step in your animation to wait for a sound to compete, AVAudioPlayer takes a delegate that it will call once the sound is done playing.
Here's how I would suggest reworking your animation sequence:
Before you begin your animation sequence, set up an AVAudioPlayer and make your view controller it's delegate.
Write an animateWithDuration:animations:completion call where the animations block does your fade in.
In the completion block, start an AVAudioPlayer playing.
In the AVAudioPlayer delegate method audioPlayerDidFinishPlaying:successfully:, invoke another animateWithDuration:animations:completion: method to fade your view out again.

AVAudioPlayer delaying setImage from happening

Disclaimer: I'm relatively new to iOS programming, and am trying to teach myself the basics as I go along just for fun... so be gentle :-)
I'm building an app that plays a series of .mp3 files strung together using AVAudioplayer. So far, that part is working good. IBActions, and IBOutlets hooked up. Click the button, and the audio plays.
My problem is this:
My play button has been added directly to the main.storyboard, set and custom with it's initial state defined in Interface Builder. When I click the play, I want to change the image of the button to indicate that it's playing. However, as soon as the audio starts, it almost seems like the view is... paused, or something. The button image stays the same, until the audio done playing. Only then, will the button image update itself -- hardly useful.
I've also just tried to set the image of of another random UIImageView that's not attached to this particular button. Same experience. As soon as I comment out my playAudioArray method, the button swaps work immediately.
-(IBAction)start_playing:(UIButton*)pressed_button {
[_btn_start setImage:[UIImage imageNamed:#"btn_on-active"] forState:UIControlStateNormal];
[self playAudioArray];
}
I hope I've left enough to go on... Any thoughts / comments / suggestions??
Thanks a million
- Drew
#
Update:
I've found that updating the view breaks down when I have my audio player in a lopp. This code works fine...
if (audioPlayer.playing == YES) {
[audioPlayer pause];
[sender setImage:[UIImage imageNamed:#"btn_on.png"] forState:UIControlStateNormal];
[_btn_stop setImage:[UIImage imageNamed:#"btn_off.png"] forState:UIControlStateNormal];
} else {
NSString *path = [[NSBundle mainBundle] pathForResource:#"audio_file1" ofType:#"mp3"];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
audioPlayer.volume = 1.0;
audioPlayer.numberOfLoops = 0;
[audioPlayer setMeteringEnabled: YES];
[audioPlayer play];
//changes the image when the button is pressed
[sender setImage:[UIImage imageNamed:#"btn_on.png"] forState:UIControlStateNormal];
[_btn_stop setImage:[UIImage imageNamed:#"btn_off.png"] forState:UIControlStateNormal];
}
But this code doesn't below doesn't.
} else {
//changes the image when the button is pressed
[sender setImage:[UIImage imageNamed:#"btn_on.png"] forState:UIControlStateNormal];
[_btn_stop setImage:[UIImage imageNamed:#"btn_off.png"] forState:UIControlStateNormal];
NSArray *theSoundArray = [NSArray arrayWithObjects:#"audio1",#"audio2",#"audio3",nil];
for (int i = 0; i < totalSoundsInQueue;) {
NSString *sound = [theSoundArray objectAtIndex:i];
// Wait until the audio player is not playing anything
while(![audioPlayer isPlaying]){
NSString *path = [[NSBundle mainBundle] pathForResource:#"mom" ofType:#"mp3"];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
audioPlayer.volume = 1.0;
audioPlayer.numberOfLoops = 0;
[audioPlayer setMeteringEnabled: YES];
[audioPlayer play];
i++;
}
}
}
The audio plays fine, loops through all my sounds and no errors occur. Only, the 'on-state' of my button only gets set AFTER the loop is done playing. I've tried this with a number of different types of looping methods. For, for each, while... every time I start to loop through sounds audio playback works great, but the display looses all focus.
Which reminds me. Once I'm in the loop, my "Stop" button looses it's a clickable (tappable) nature. Until the loop has finished running. If I'm not in a loop, the stop button works fine, no problems.
Sorry if I'm making a total newb mistake here... thoughts / comments anyone?
Your for loop is tying up the main thread, making it your UI unresponsive. Instead of running the for loop continuously, set yourself as the delegate of AVAudioPlayer. Once the audio player is done playing it will send the delegate the message - (void) audioPlayerDidReachEnd; (which you'll have to implement). Then you can play the next audio file in the queue in that implementation. You really want to set yourself up as the delegate anyway. AVAudioPlayer will send your delegate messages for interruptions (like a call coming in) and decode errors. You want to handle these events gracefully.
I think the reason the audio is playing fine is that is has a higher priority and runs on a separate thread. The reason the UI becomes un-responsive is even though animations/UI changes occur on a separate thread, they're initiated from the main thread. But all UI changes are queued to the end of a run-loop (so optimizations can be made). However, that run loop never ends because your for loop never ends until all the audio files are played. The audio files can be played in order because they're initiated in that for loop and run on a separate thread. Makes sense? In general, you really don't want to tie up the main thread.

Change view before playing sound in Objective-C

I have a program which plays two sounds. I want the user to know which sound is being played, so I have two large images of buttons. I have three different views that I would like to swap between - one where both buttons are red (nothing is playing), and two where one of the two buttons is green (to show which is playing).
The main problem I am getting is that my sound plays and finishes before the view has swapped! (I am using [window setContentView:firstSound] before the sounds are played)
The altered view eventually does show up, but only after the method playing the wavs has finished.
I am playing the sound files using NSSound (they are both short wav files, so I thought it appropriate)
Anyone know what I have done wrong? Or if there is another way I could do this? Thanks heaps!!
Edit: I have followed the advice to make it so that only the image is changed (rather than the view), but the same problem occurs. The update happens after the sounds have finished playing.
Here's the code:
-(void)readWavs{
//[window setContentView:firstWav];
[redButton setHidden:YES];
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"fish" ofType:#"wav"];
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:resourcePath byReference:YES];
[sound play];
sleep(3); //needed so that the sound will actually play
[sound stop];
[sound release];
//[redButton setHidden:NO];
//[window setContentView:testView];
sleep(2);
//[window setContentView:secondWav];
NSString *resourcePath2 = [[NSBundle mainBundle] pathForResource:#"fish" ofType:#"wav"];
NSSound *sound2 = [[NSSound alloc] initWithContentsOfFile:resourcePath2 byReference:YES];
[sound2 play];
sleep(3);
[sound2 stop];
[sound2 release];
}
The green 'on' button should show up before the sound plays, but it only does so after the method is done.
I'm not sure if I'm understanding correctly, but what it sounds like you are trying to switch to a whole different view when a sound is played that displays which sound is being played (by images)? If so, you should just use imageView.hidden = YES and imageView.hidden = NO to display the correct images based on the sound. Better yet, you could use one image view and change the image displayed in the image view based on the sound.
You're blocking the run loop with those sleep()s. The display isn't updated until all the processing for the current event is finished, and that doesn't happen until this readWavs method is finished.
If you want to do something after the sound is finished, assign it a delegate (probably the same object that started the sound) and implement the NSSoundDelegate -sound:didFinishPlaying: method. Then, in readWavs, just start the sound and return - your delegate method will be called when it finishes, and because readWavs returns immediately, it won't block the run loop.