How to display images in array, pause between each image? [duplicate] - ios

This question already has answers here:
Calling sleep(5); and updating text field not working
(4 answers)
Closed 8 years ago.
In my Application, I have a NSMutableArray with UIImage in it.
I would like to display the first UIImage in the array for three seconds, and then to
display the second image.
All of this should happen when I press a UIButton.
Below is my code:
[testImageView setImage:[arr objectAtIndex:0]] ;
sleep(3) ;
[testImageView setImage:[arr objectAtIndex:1]] ;
testImageView is a UIImageView object on my screen.
When I'm running this code, my button remain pressed for three seconds and only the second image is displayed.
What should I do?

Try using one of the most obscure methods of UIImageView.
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:...];
myImageView.animationImages = images;
myImageView.animationDuration = 3.0 * images.count;
[self.view addSubview:myImageView];
Then, when you wanna start animating
[myImageView startAnimating];
I don't know what you plan on doing with this, but 3 seconds might be too much. If you're doing some sort of presentation-ish, then this method might not be very good, since there's no easy way of going back.

The reason that your button remains pressed is because you slept main thread for 3 seconds, this means that nothing is going to happen to your application (at least the user interface) until thread is back to active.
There are multiple ways to achieve what you wish, but you must put a timer on the background thread. I would suggest you put the code to set the second image in another method first and make array into a property.
- (void)setImage
{
[testImageView setImage:self.yourArray[1]];
}
Then you can use one of the following ways to execute the method:
Use a NSTimer:
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(setImage) userInfo:nil repeats:NO]
Use performSelector method of NSObject:
[self performSelector:#selector(setImage) withObject:nil afterDelay:3.0];
Use Grand Central Dispatch.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3.0), dispatch_get_main_queue(), ^{
[self setImage];
});
Any of the ways described above will work.
Read more information about concurrency on the links below:
http://jeffreysambells.com/2013/03/01/asynchronous-operations-in-ios-with-grand-central-dispatchNS
https://developer.apple.com/library/ios/documentation/general/conceptual/concurrencyprogrammingguide/Introduction/Introduction.html
how to call a method of multiple arguments with delay
How can I delay a method call for 1 second?

Related

iOS UI elements won't update while in the method called

I'm doing a pretty simple iOS/ObjC program. Click a button, it's loops thru a for..next and displays counters and pics.
for (int i=0; i <= numberOfExercises; i++) {
exerciseName = [exercises objectAtIndex:j][0];
[self.lastLabel setText:exerciseName];
[self.lastLabel setNeedsDisplay];
usleep(1000000);
NSLog(#"Count: %d Name:%#", i, exerciseName);
}
However, it's not updating the actual textfield on the screen.
I've tried everything I know of and there's just something I can't see.
(IBAction)btnExerciseClicked:(id)sender {
for (int i=0; i <= numberOfExercises; i++) {
exerciseName = [exercises objectAtIndex:j][0];
exerciseImageName = [exercises objectAtIndex:j][1];
exerciseImageName = [exerciseImageName stringByAppendingString:#".jpg"];
self.exerciseImage.image = [UIImage imageNamed:exerciseImageName];
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
[self.exerciseImage setNeedsDisplay];
}
I've put the block in there:
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"inside the block");
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
[self.exerciseImage setNeedsDisplay];
});
And I've done a direct assign before the loop starts:
exerciseName = [exercises objectAtIndex:5][0];
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
NSLog(#"Name: %#", self.ExerciseName.text);
I've even written a separate method for it:
-(void) myCycleDisplay: (NSString *) imageName
nameOfExercise: (NSString *) exerciseName
voiceOver: (BOOL) useVoiceOver
countDown: (BOOL) useCountDown
beep: (BOOL) useBeep{
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
self.exerciseImage.image = [UIImage imageNamed:imageName];
}
And after all that, STILL no display until AFTER the method is done. Put in a delay: usleep(1000000);
I've confirmed that the data is in the element. I've setup the element in code and in IB.
The app is pretty simple, a button is pressed on the screen, data is loaded, an array is walked and display items are updated based on elements in the array.
The data is in the textfield, this has been confirmed. It even display them, but only after the method is exited.
Once the method is exited, the text/pic are displayed properly.
So, as a test, I made the for..next loop run twice and clicked the button over and over. Sure enough it displayed properly AFTER leaving the method.
I can't get it to update the display while IN the method. (this also includes the slider).
Why do I have to exit a method to get the display to update?
I think the easiest solution to your problem is to dump the whole for-loop into another queue.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
for (int i=0; i <= numberOfExercises; i++) {
NSString *exerciseName = [exercises objectAtIndex:i][0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.lastLabel setText:exerciseName];
[self.lastLabel setNeedsDisplay];
});
usleep(1000000);
NSLog(#"Count: %d Name:%#", i, exerciseName);
}
});
There are three additional changes.
Changed [exercises objectAtIndex:j] to [exercises objectAtIndex:i]
I think this was a mistake on your part
Made exerciseName local to the block.
I could have made the declaration of exerciseName __block, but it's easier to just make the whole thing local.
Wrapped setting the label in dispatch_async(dispatch_get_main_queue(), ^{ … });
UI updates must be made on the main queue.
Note, this is a bad solution. You should rethink your approach entirely. I would move this logic into it's own separate class, then use notifications to get the UI to update.
When you call usleep(1000000) you are blocking the main queue. UI updates happen on the main queue, but you're blocking that queue, so they don't happen. Also, UI updates in general don't happen until you finish the current pass through the event loop-- from the user tapping your button, through your method doing whatever it needs to do, continuing until your method finishes. Then UIKit updates the UI. You need to let your method finish because that's how UIKit works.
I'm not exactly sure what you're trying to do. If you want to update your UI at intervals, look into NSTimer.
I hope I understood the problem well enough to give a relevant answer and I apologize if I didn't. Here's how I see it:
There is a set of exercises that have to be done one after another;
Exercises take certain amount of time;
The name of the exercise and a relevant image are displayed when exercise starts.
The current implementation does precisely that: display is updated and the system sleeps for a certain amount of time before updating the display again.
The problem with that approach is that UIKit frameworks that is commonly used when doing interactive things on iOS devices is what could be called "indirect". For instance, when .text property of a label is updated, text is not drawn, instead, the system is notified that display should be updated. UIKit periodically checks whether updates are requested and if they are, the display is redrawn.
All of this is done on the "main queue" by sequentially executing blocks of code added to it (as a side note, this is to unlike the way Javascript work in a browser). This means that as long as some block of code is executed, everything else of the main queue will not be and the app will stop being interactive and updating display. Thus, the way to use UIKit is by quickly notifying it of necessary changes and finishing the function. Consequently, one never does long calculations or "sleeps" on on the main queue.
In context of this question, this leaves a problem of timed updates: we want to update the UI after some time has expired. There are 2 solution that come to mind:
The "typical" one would to be use the NSTimer class;
If constant updates to the UI are necessary, there is a CADisplayLink class and a convenient [UIScreen displayLinkWithTarget:selector:] method to create one on, say, [UIScreen mainScreen]. What it does is, it calls the selector in time for screen updates (currently, that's about 60 times per second but you can configure it to be called less frequently). It is ideal for things like count-down timers and frequently updating UI elements such as progress bars.
(I've switched to Swift and the latest API so the method names might not be exactly right in Objective-C).
Please let me know if I misunderstood the question or if you need sample code or further clarification.
Good luck!

AppleWatch circle progress (or radial arc progress) with images error

I'm developing an app for Apple Watch using WatchKit and I need to implement a circular progress while recording audio, similar to Apple demos.
I don't know if WatchKit includes something to do it by default so I have created my own images (13 for 12 seconds [209 KB in total]) that I change with a timer.
This is my source code:
Button action to start/stop recording
- (IBAction)recordAction {
NSLogPageSize();
NSLog(#"Recording...");
if (!isRecording) {
NSLog(#"Start!");
isRecording = YES;
_timerStop = [NSTimer scheduledTimerWithTimeInterval:12.0
target:self
selector:#selector(timerDone)
userInfo:nil
repeats:NO];
_timerProgress = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(progressChange)
userInfo:nil
repeats:YES];
} else {
NSLog(#"Stop :(");
[self timerDone];
}
}
Action when timer is finished or if tap again on the button
-(void) timerDone{
NSLog(#"timerDone");
[_timerProgress invalidate];
_timerProgress = nil;
[recordButtonBackground setBackgroundImageNamed:[NSString stringWithFormat:#"ic_playing_elipse_12_%d", 12]];
counter = 0;
isRecording = NO;
}
Method to change progress
- (void)progressChange
{
counter++;
NSLog(#"%d", counter);
NSString *image = [NSString stringWithFormat:#"ic_playing_elipse_12_%d", counter];
[recordButtonBackground setBackgroundImageNamed:image];
NSLog(image);
}
This is a gif showing the bug. It starts to show but change the image randomly until it gets a pair of seconds. (Note: I have already checked that the first images have the right progress)
Update (other solution): using startAnimating
There is a new way to show a circle progress using startAnimatingWithImagesInRange:duration:repeatCount
You need to specify the images range and the duration for your animation. See an example below:
[self.myElement startAnimatingWithImagesInRange:NSMakeRange(0, 360) duration:myDuration repeatCount:1];
I believe this is a side-effect of the way WatchKit interprets image names.
Image names ending in numbers are used to represent frames in an animated image, so "ic_playing_elipse_12_10" is being interpreted as the first frame of the image named "ic_playing_eclipse_12_1" and your 10th image gets displayed when you want it to display your first one.
You should be able to just change your image names so the number isn't the last part of the image name and it will fix it.
I know that this question is answered but I would like to explain better the solution of startAnimating.
[myElement startAnimatingWithImagesInRange:NSMakeRange(imageIdBegin, imageIdEnd) duration:duration repeatCount:repetitions];
In order to use this, you need a suite of images named like you want but with sequential ids. For example: progress_0.png, progress_1.png, progress_2.png, .., progress_n.png
You can decide the begin index and the end index using that values in NSMakeRange(imageIdBegin, imageIdEnd) just indicating the number id (not the full image name)
Of course, you will need to indicate the duration of the animation in seconds, WatchKit will interpolate for you to animate your images between init index and end index.
And finally, you can indicate if you want to repeat this animation (for example for a loading action) or not. If you don't want to repeat, you have to indicate 1.
I hope this can help more people to perform circle progress or another animations inside their AppleWatch applications :)

Whats the best way to animate images to my apporach?

I have used
image.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"image1"],
[UIImage imageNamed:#"image2"],
[UIImage imageNamed:#"image3"],
[UIImage imageNamed:#"image4"], nil];
[image setAnimationRepeatCount:1];
image.animationDuration = 1;
[image startAnimating];
but it gives a flip book effect to the animation. What I am looking for is a smooth animation to set each image at given time. I have currently is NSTimers to trigger a method then I have code within the method to set a image and another NSTimer to set another image which is in a method so fourth. It gives the animation I am looking for but the problem I am facing is that I am developing a game and I want when the user pauses the game it stops on the current image it is on. The game runs on a NSTimer which triggers a method. All I do to pause the game is to invalidate the NSTimer.
What approach should I take?
you should add NSTimer and user_flag input.
This user_flag input you can declare as static int user_flag = 0
and when you are doing stop then simply make this variable as user_flag = 1.
Now implement the logic in all this condition that will look something like this one :
if(NSTimer_obj == condition && user_flag == 0)
{
//your code...
}

How to show buffered data on UISlider using MpMoviePlayerController in iOS?

I am using MPMoviePlayerController to play audio and I want to show the buffered data on slider like this ...
I want to show the buffer data like red section in slider. I have tried to google it. But I didn't get any solution for it.
and How to make slider customize?
Thanks in advance...
Yes We can Show stream data using MPMoviePlayerController by its playableDuration. I am explaining all these steps involved me to make this custom_slider for my customize player...
(You Can direct read step 4 if you only interested in programming part)
These are the steps:
Step 1: (First Design part) i done it using standard Controls...
In Interface Builder place your UISlider immediately on top of your UIProgressView and make them the same size.
On a UISlider the background horizontal line is called the track, the trick is to make it invisible. We do this with a transparent PNG and the UISlider methods setMinimumTrackImage:forState: and setMaximumTrackImage:forState:.
In the viewDidLoad method of your view controller add:
[ProgressSlider setMinimumTrackImage:minImage forState:UIControlStateNormal];
[ProgressSlider setMaximumTrackImage:transparentImage forState:UIControlStateNormal];
[progressBar setTrackImage:maxImage];
[progressBar setProgressImage:streamImage];
where ProgressSlider refers to your UISlider and progressBar to your UIProgressView.
and you can make transparent image programmatically like this.
UIGraphicsBeginImageContextWithOptions((CGSize){512,5}, NO, 0.0f);
UIImage *transparentImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();
you can change the size of it according to your progress view track image height width... I use {512,5} you can use {1,1} for default slider and progress view.
Reference taken form this answer https://stackoverflow.com/a/4570100/3933302
Step 2:
Now I tried to set the track and progress image of UIProgressView. But it was not showing the track and progress image. then i found this solution...to use this library available in github. https://gist.github.com/JohnEstropia/9482567
Now I changed occurrences of UIProgressView to JEProgressView, including those in NIBs and storyboards.
Basically, you'd need to force assigning the images directly to the UIProgressView's children UIImageViews.
The subclass is needed to override layoutSubviews, where you adjust the heights of the imageViews according to the image sizes.
Reference taken from this Answer https://stackoverflow.com/a/22322367/3933302
Step 3:
But now the question is from which will you make the image for slider and progressView. This is explained in this tutorial and You can download the psd also.
http://www.raywenderlich.com/32167/photoshop-tutorial-for-developers-creating-a-custom-uislider
Step 4:
Now come to programming part..
by using the playable duration it is possible to show stream data..(which was my biggest problem)
Using MPMovieLoadStatePlayable we can get when The buffer has enough data that playback can begin, but it may run out of data before playback finishes.
https://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/MPMoviePlayerController_Class/index.html#//apple_ref/c/tdef/MPMovieLoadState
Now in your PlayAudio method...place this code..
-(void) playAudio:(NSURL *) url {
.............
streamTimer= [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(loadStateChange) userInfo:nil repeats:YES];
..........
}
and here is the definition of loadStateChange
-(void)loadStateChange
{
if(self.moviePlayer.loadState == MPMovieLoadStatePlayable){
[self.moviePlayer play];
slideTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(updateProgressUI)
userInfo:nil
repeats:YES];
[streamTimer invalidate];
}
else if(self.downloadList)
//if you are playing local file then it will show track with full red color
progressBar.progress= appDel.moviePlayerController.moviePlayer.duration;
}
and here is the definition of updateProgressUI
- (void)updateProgressUI{
if(self.moviePlayer.duration == self.moviePlayer.playableDuration){
// all done
[slideTimer invalidate];
}
progressBar.progress = self.moviePlayer.playableDuration/self.moviePlayer.duration ;
}
I hope this fully customize slider will help you lot in making custom player...I explained all my work in steps.....Thanks ....
You cannot do this with MPMoviePlayerController.
However if you switch to AVPlayer then you can use AVPlayerItem.loadedTimeRanges. It contains all information you need for visualization in your custom control.
There are number of questions that have been asked on that topic, for example:
AVPlayer streaming progress
According the documentation of MPMoviePlayerController (available here) you can use the playableDuration property to retrieve the duration that has been loaded by the player.
You would then have to subclass UISlider to draw the red part of your control in the drawRect: method.
As for AVPlayer you would call loadedTimeRanges (documentation here) on the AVPlayerItem representing your content. That would give you an array of (as of iOS 8) a single CMTimeRange representing the part of your media that has been buffered. I would advise using [NSArray firstObject] when retrieving the time range to protect your code against a possibly empty array.

NSTIMER seems to be ignored

I have created a program and found an error that i cannot seem to work out.
I have stripped the problem out to recreate in a brand new project but it still re occurs.
The problem is when i press the BUTTON the image changes but at a much faster speed than the speed set up in NSTIMER but only the first time the button is pressed.
If i carry on pressing the button the image changes at the speed i require.
IT ONLY HAPPENS ON THE FIRST TIME ROUND AND I AM USING A TOUCH DOWN EVENT ( ALTHOUGH I HAVE TRIED TOUCH UP INSIDE )
This only happens the first time and is extremely important that the first time is the same timing as the rest.
I am aware of various discussions as to the accuracy of NSTIMER anyway , but i dont think it is relevant to my question
here is my .h
- (IBAction)slap:(id)sender {
NSString *imagechange4 = [NSString stringWithFormat:#"onehandedplayer2.png"];
//player2 is an UIButton IBOutlet
[player2 setImage:[UIImage imageNamed:imagechange4]];
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(handsback1) userInfo:nil repeats:NO];
}
-(void)handsback1 {
NSString *imagechange3= [NSString stringWithFormat:#"hands rotated.png"];
[player2 setImage:[UIImage imageNamed:imagechange3]];
}
Try using performSelector -
[self performSelector:#selector(handsback1) withObject:nil afterDelay:0.5];
It's really easy & reliable.
Just had an idle couple of minutes (!) & came across this again - it occurred to me that you could be mistaking the button highlighting when pressed as the image change - have you unchecked the button's "Highlighted Adjusts Image" property in IB's Attributes Inspector?
Unlikely perhaps, but you never know...

Resources