Display random pictures with timer delay - ios

I am making an application that randomly selects a picture from a preset group of pictures and displays it to a image view. This should happen every second or so until it has gone through 20 cycles.
Hear is my header and implementation code:
#interface spinController : UIViewController
{
IBOutlet UIImageView *imageHolder;
NSTimer *MovementTimer;
}
-(IBAction)Button:(id)sender;
-(void)displayPic;
#end
#implementation spinController
-(IBAction)Button:(id)sender
{
int count = 0;
while (count <20)
{
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic) userInfo:nil repeats:NO];
count++;
}
}
-(void)displayPic
{
int r = arc4random() % 2;
if(r==0)
{
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else
{
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#end
I have made this application in a much more advanced form in WPF and ran into similar issues where the pictures do no cycle through properly. If I hit spin it randomizes but does not go through the 20 cycles... just one. This is my first application in objective-c and realize the efficiency of the method I choose will determine how good my application will run in a more complex form. Any help would be greatly appreciated.

The problem is that you're calling the timer repeatedly within the while loop; and since that particular while loop will complete within a millisecond or so, you're creating 20 timers one after another in immediate succession. Because of this, only the final image will show up in the imageHolder view. Edit: Even if the loop were to take more than a millisecond per iteration, I believe the NSTimer wouldn't actually fire until the method exits anyway.
In order to show the images one after another as you're trying to do, (1) use the NSTimer without the while loop, (2) keep track of the iterations using a count class instance variable so as not to lose the value of the variable upon the completion of the various methods, and (3) pass along the NSTimer to the displayPic method so you can invalidate the timer from there upon the 20th iteration. For example:
// Declare the "count" instance variable.
int count;
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(IBAction)Button:(id)sender {
// The count starts at 0, so initialize "count" to 0.
count = 0;
// Use an NSTimer to call displayPic: repeatedly every 1 second ("repeats" is set to "YES")
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic:) userInfo:nil repeats:YES];
}
// Pass along the NSTimer to the displayPic method so that it can be invalidated within this method upon the 20th iteration
-(void)displayPic:(NSTimer *)timer {
// Get the random number
int r = arc4random() % 2;
// If the number is 0, display puppy1.jpg, else display puppy2.jpg.
if(r == 0) {
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else {
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
// Increment "count" to reflect the number of times the NSTimer has called this method since the button press
count ++;
// If the count == 20, stop the timer.
if (count == 20)
[timer invalidate];
}
#end

Change repeats to YES. This causes the timer to run again and again. Then instead of a while loop check the count in the method itself.

Related

iOS sequencer tempo slider is causing app to crash

I have created a drum sequencer using Objective C. I want the slider to control the tempo. At the moment, everything works, and the interval between each step is being controlled by:
while (self.running)
{
// sleep until the next step is due
[NSThread sleepUntilDate:time];
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// time duration until next step
time = [time dateByAddingTimeInterval:0.2];
}
So the time between each step is 0.2 seconds. I have tried to implement a tempo slider like so in the view controller .m (the slider has a range of 0.3 to 1.0 so will output similar value to what is currently in time):
- (IBAction)sliderMoved:(UISlider *)sender
{
AppDelegate* app = [[UIApplication sharedApplication] delegate];
app.tempo = sender.value;
}
and by changing the line in the while(self.running) thread to:
time = [time dateByAddingTimeInterval: (NSTimeInterval) _tempo];
However, this causes the time between steps to be far too short (tempo is crazy fast) and when any control in the app is touched, it crashes.
I wonder if I need to set up a function like this, but I'm not sure what would go inside to enable the tempo slider to work:
- (void)setTempo:(float)tempo
{
}
I have tried to be as clear as I can, if anyone can help me I'd be very grateful, thanks in advance
-(void) startDrumTick{
[self.myDrumTimer invalidate]; // stop any current existing timer
// perform the call to the method 'drumMethodOperation:'
// every 0.2 sec. NB: drumMethodOperation will run on main thread.
// this means that if you expect to do long-blocking operation,
// you will need to move that op to an async thread, in order to avoid
// the UI blocking
self.myDrumTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(drumMethodOperation:)
userInfo:nil
repeats:YES];
}
-(void)drumMethodOperation:(id)sender
{
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// any other needed operation to run every 0.2 secs
}
Below an example for an async thread management using GCD
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Long blocking operation ( DO NOT PERFORM ANY UI OPERATION, like changing a text label, setting an image to an UIImageView, etc. )
[self myLongDbQuery];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Perform you UI Updates here
self.myLabel.text = #"Query done!!!";
});
});
Hope it helps
Luca is right about using GCD. If talk about your initial solution.
Did you set initial value for _tempo? Looks like your bug can be caused by _tempo = 0 initially. As you understand sliderMoved will be called only after some user action so you need to set initial value.

Play sounds after different time intervals

I want to use a speech synthesised sentence in an application demo. After pressing a button, a timer runs and after for example 12 seconds the first sentence is being spoken, then after 1.30min and so on.
The approach I was thinking of, is an NS Timer. But as far as I can see it only plays after a defined time. So do I need for any timespan a new timer? or can I track the time left and invoke a method call when a specific time is reached?
Thanks
I would do this using a dictionary with NSNumbers as the keys (representing seconds), and the sentence you want spoken as the value. Use a simple timer firing once every second, that updates an int, which (when converted to an NSNumber) would be checked against the dictionaries keys. I'm just logging in this example, but this should get you started,
- (void)viewDidLoad {
[super viewDidLoad];
self.dict = #{#3:#"First", #5:#"second", #11:#"Third", #35:#"Fourth"};
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(doStuff:) userInfo:nil repeats:YES];
}
-(void)doStuff:(NSTimer *) aTimer {
static int i = 0;
i++;
if ([self.dict.allKeys containsObject:#(i)]) {
NSLog(#"%#", self.dict[#(i)]);
}
}

NSTimer change interval performance

My app adds a custom SKNode object to a SKScene at a fixed interval (sub second) using an NSTimer. At certain times I want the timer to speed up. Here's what I do (code simplified):
#interface MyScene : SKScene
#end
#implementation MyScene {
MyNode *node;
NSTimer *timer;
int speed;
}
- (id):initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
speed = 0.8f;
timer = [NSTimer = scheduledTimerWithTimeInterval:speed target:self selector:#selector(addNodeToScene) userInfo:nil repeats:YES];
}
}
- (id):addNodeToScene {
if (node != nil) {
[node removeFromParent];
node = nil;
}
CGPoint location = //random x y coordinates using arc4random()...
node = [[MyNode alloc] initAtLocation:location];
[self addChild:node];
}
// at some point I call this method (called regularly throughout life of app)
- (id):speedUp {
[timer invalidate];
timer = nil;
speed *= 0.9f;
timer = [NSTimer = scheduledTimerWithTimeInterval:speed target:self selector:#selector(addNodeToScene) userInfo:nil repeats:YES];
}
I've noticed a slight lag every time i call speedUp. I'm very new to Objective-C so not sure how to resolve. Is it more efficient to use dispatch queues over a timer or can I not avoid this issue because the internal is so fast?
I ran time profiling on instruments and this was not highlighted - instead my heaviest method was addNodeToScene.
My guess is that you're calling -speedUp outside of the normal work in -addNodeToScene. If so, the lag is likely coming from the fact that you've already used up a bit of the previous delay, then -speedUp invalidates the old timer and creates a new one, which starts the delay all over again. You'll see a lag any time you're more than 10% beyond the previous timer fire.
I'll try some ASCII art to illustrate. + is a timer fire, * is when you call -speedUp and it resets the timer.
+---------+--------+---------+---------+---------+
+---------+---*--------+--------+--------+--------+
Found the solution in another question: Xcode / Objective-C: Why is NSTimer sometimes slow/choppy?
(use CADisplayLink instead of NSTimer)

xcode calling an int redefining value of an int when called in another function

I'm having trouble with incrementing an int from one function by calling it in another function.
At the moment the bit I'm working on looks like this:
in the .h file I declare the int and timer as this:
int count;
NSTimer *sequenceOn;
in the .m file the segment of my function looks like this:
-(void) sequence {
count = 1;
while (count < (target)) {
sequenceOn = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(imagePlayer) userInfo:nil repeats:NO];
}
}
-(void)imagePlayer {
--CODE HERE FOR PLAYING ANIMATION--
count = count + 1;
}
All my other code is working fine and it should play through a series of images using the count value to decide which one to play. At the moment though it only plays the first animation and wont increment to the next one.
Any help would be much appreciated.
Thanks.
The timers will be fired from the run loop, which your code is blocking while running the loop. You probably want to have a timer regularly call your method:
-(void)stopTimer {
[sequenceOn invalidate];
sequenceOn = nil;
}
-(void)sequence {
[self stopTimer];
count = 1;
// start a repeating timer:
sequenceOn = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self
selector:#selector(imagePlayer) userInfo:nil repeats:YES];
}
-(void)imagePlayer {
//--CODE HERE FOR PLAYING ANIMATION--
count = count + 1;
if (count >= target) {
[self stopTimer];
}
}
I don't see anything in your while loop that increments count or decrements target, so either it's an infinite loop or it never executes, depending on the value of target.
You have the last argument of scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: set to NO, is that what you want, it will only fire once. If not you will have to give us more code to show how imagePlayer is being called repeatedly.

How do I execute my method when the UIView loads?

I have a view-based template app and have UILabel & UIButton. For debugging purposes I'm showing and hiding the button whilst changing the UILabel.text.
In C++ I would 'thread root();' to execute the root method but I don't know how to in Objective-c. How to run my 'root' method once the view loads?
-(void) root
{
[bombButton1 setHidden:NO];
int s = 0;
int j = 10;
while ( s < j )
{
[bombButton1 setHidden:YES];
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
s++;
}
Edit:
Right, now I have: (but I get ERROR: Expected method body on the "-(void) rootMethod: NSTimer * timer {" line)
-(void) applicationDidFinishLaunching : (UIApplication *) application {
spawnTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(rootMethod:) userInfo:nil repeats: YES];
}
-(void) rootMethod: NSTimer * spawnTimer {
int s = 0;
int j = 10;
while ( s < j )
{
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
//debugLabel.text=#"debug test complete";
s++;
}
}
Several ways to do this, I think. Here's one:
[self performSelectorInBackground:#selector(root) withObject:nil];
You'd make this call in say, your -(void)viewDidAppear: method.
You may run into issues running code on threads other than the main thread that tries to manipulate the UI.
That sleep(1) is worrisome. You could use a repeating NSTimer instead and eliminate the sleep(1) entirely. Something like:
[NSTimer scheduledTimerWithInterval:2.0 target:self
selector:#selector(root:) userInfo:nil repeats:YES];
For NSTimer, you'd have to change your method, root, to have a signature like
- (void)root:(NSTimer*)theTimer
You need to implement a called viewDidLoad.
- (void) viewDidLoad() {
// your code here
}
I'm sure you have your reasons, but you really shouldn't iteract with UI components in anything other than the UI thread. What you actually need to do is use an NSTimer to call a method on the UI thread multiple times.
What you should be doing is performSelectorOnMainThread when you want to update the UI Thread.
Do your running in the background and update variables that will contain the updated values, then use performSelectorOnMainThread on the View, sending it to a method that will merely update the Textbox with the data in the variables.
You can do anything in a background thread, except update the ui.
Edit: Furthermore I dont recommend using Timers in place of background threads, I have had instances when using Timers, where only so many get created and when I expected a background thread to fire, it never did. The timer actually never fired, even tho it was created.

Resources