I have sub-classed a component (it looks like circle and there is a picture inside it (NSView subclass)).
I want to change the picture every X time (to make it look like an animation).
When in my main view controller I drawing 1 sub-class like this everything works Fine, but when I adding more the picture changes much faster with each.
(I am firing the picture change using an NSTimer)
I am assuming that the issue happens because I have multiple NSTimers on the same queue
However I tried to use
NSTimer *uiTimer = [NSTimer timerWithTimeInterval:(1.0 / 5.0) target:self selector:#selector(changePicture) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:uiTimer forMode:NSRunLoopCommonModes];
It didn't solve the issue (I assume because it is still on the main thread)
So I came up with NSThread Solution
[NSThread detachNewThreadSelector:#selector(updateModel) toTarget:self withObject:nil];
- (void) updateModel
{
[NSTimer scheduledTimerWithTimeInterval:secondsBetweenUpdates
target:self
selector:#selector(changePicture)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
It has a bit different behaviour (but still no luck as more sub-classes I have as faster picture changes).
My last shoot was this solution:
// Update the UI 5 times per second on the main queue
// Keep a strong reference to _timer in ARC
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, (1.0 / 5.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
[self changePicture];
});
// Start the timer
dispatch_resume(_timer);
I am not usually giving up , but I am trying to resolve it for 3 days already... And I think I need and advice of how to do this so it will work as intended.
If you are using GCD I would recommend you use dispatch_after, for example:
float delayTime = 0.2f;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delayTime * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self changePicture];
});
Related
When click on the image in my app and the image is at the top, the image will always move down perfectly. But when the image is at the bottom sometimes the image does not move up. Even though it does not move the process is still run and it logs as if it was at the top. I tried everything I know how to do but do you see anything wrong? Thanks!!
- (void )imageTapped:(UITapGestureRecognizer *) gestureRecognizer
{
NSLog(#"imageTapped");
[UIView beginAnimations:#"move" context:nil];
[UIView setAnimationDuration:0.3];
if (self.imgV.frame.origin.y == 20) {
NSLog(#"Top");
upOrDown = #"1";
self.invalidateTheTimer = #"";
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
self.imgV.frame=CGRectMake(0, self.view.frame.size.height-self.imgV.frame.size.height, self.imgV.frame.size.width, self.imgV.frame.size.height);
self.vBottom.frame=CGRectMake(0, self.imgV.frame.origin.y+self.imgV.frame.size.height, self.vBottom.frame.size.width, self.vBottom.frame.size.height);
} else if (self.imgV.frame.origin.y >= 507) {
NSLog(#"Bottom");
self.imgV.frame=CGRectMake(0, 20, self.imgV.frame.size.width, self.imgV.frame.size.height);
self.vBottom.frame=CGRectMake(0, self.imgV.frame.origin.y+self.imgV.frame.size.height, self.vBottom.frame.size.width, self.vBottom.frame.size.height);
[hour setText:#""];
[minute setText:#""];
[second setText:#""];
[timer invalidate];
self.invalidateTheTimer = #"INVALIDATE";
}
NSLog(#"%f", self.imgV.frame.origin.y);
[UIView commitAnimations];
}
Considering you haven't posted your entire code, it's hard to figure out what's wrong exactly, but looking at the mess you've made, I suggest you take a look into block based animations which should simplify what you're trying to accomplish.
Additionally, if I had to guess, I'd say the issue is in your timer and whatever method it's invoking is probably messing with your animations.
With the help of a few threads here I created a slideshow that starts as soon as my view loads
However I am trying to make a slight modification where, I want the images to be random Not showing in the order in the array or in any particular order. This is my codde
-(void)viewDidAppear:(BOOL)animated
{
NSArray *myImages=[NSArray arrayWithObjects:
[UIImage imageNamed:#"Pitt Bull"],
[UIImage imageNamed:#"German Sherpard"],
[UIImage imageNamed:#"Pincer"],
nil];
int indexed=arc4random()%[myImages count];
UIImage *image=[myImages objectAtIndex:indexed];
NSArray *imageAr=[NSArray arrayWithObject:image];
[NSTimer scheduledTimerWithTimeInterval:12.0
target:self
selector:#selector(viewDidAppear:)
userInfo:Nil
repeats:YES];
[self.kenView animageWithImages:imageAr
transitionDuration:12
loop:YES
isLandscape:YES];
}
However it only works once. it loads a random picture at the start, but after that it keeps loading the same image. I can see that is because indexed is only assigned an integer when the view "did appear" . I was wondering if there was another way too select a new random number since I am not using a button/action as I saw many of other members doing. OR a better way of accomplishing this.
Thank you
You may want to try this function, + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats from NSTimer.
For example, in ViewDidLoad
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(showImage:) userInfo:nil repeats:YES];
and declare a function
- (void)showImage:(NSTimer*)timer
{
// your code to generate random index and animate image transition
}
if for some reason, you want to stop the timer
[timer invalidate];
Update
To avoid the timer messing up issue, trigger timer inside showImage, in ViewDidLoad, do [self showImage]
- (void)showImage
{
// your code to generate random index and animate image transition
// trigger the timer when the transition is finished
// You can leverage the method animateWithDuration:animations:completion:
// from UIView
[UIView animateWithDuration:12
animations:^(){
// image transition
}
completion:^(BOOL finished){
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(showImage) userInfo:nil repeats:NO];
}]
}
This should provide you the idea.
Consider using this method to randomly shuffle the elements of your array at startup.
new to the iOS SDK and Objective-C and just have a quick question regarding the NSTimeInterval.
I have a really simple game, and I want to show for how long the game have been played (so that I can save away a best time).
Now, of course if I set the label's text with something like this:
NSTimeInterval elapsedTime = [startDate timeIntervalSinceNow];
theLabel.text = [NSString stringWithFormat:#"%.2f", -elapsedTime];
The text of the label will be how long time it will have passed since the game started, which is 0 seconds.
In what way can I have the elapsedTime object "running in the background" so that the player can se for how long he/she have played at all times?
Have a look as the NSTimer class to schedule a timer for a preset time and update the label in the callback.
-(void)viewDidLoad {
[super viewDidLoad];
// Add timer at 60 frames a second
timer = [NSTimer scheduledTimerWithInterval:1.0/60.0 target:self selector:#selector(targetMethod:) userInfo:nil repeats: YES];
}
-(void) targetMethod: NSTimer * theTimer {
... update label
}
So I'm developing my first iOS application and I need help..
Simple program for now, I have about 9 buttons and when I press the first button, or any button, I just want the first button to highlight for 60 milliseconds, unhighlight, second button highlights, wait 60 milliseconds, unhighlight and so on for the rest of the buttons so it looks like a moving LED.
I've looked tried sleep/usleep but once that sleep duration is done it seems like it skips the highlight/unhighlight all together.
For example:
- (void) button_circleBusy:(id)sender{
firstButton.enabled = NO;
sleep(1);
firstButton.enabled = YES;
and so on for the rest of the buttons. It DOES delay, but it doesn't delay the "firstButton.enabled = NO;". I have a picture for each button's "disabled state" and I never see it.
Any help's appreciated! I've looked into NSTimer but I was unsure on how to implement it.
Thanks.
-Paul
sleep doesn't work because the display can only be updated after your main thread returns to the system. NSTimer is the way to go. To do this, you need to implement methods which will be called by the timer to change the buttons. An example:
- (void)button_circleBusy:(id)sender {
firstButton.enabled = NO;
// 60 milliseconds is .06 seconds
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
}
- (void)goToSecondButton:(id)sender {
firstButton.enabled = YES;
secondButton.enabled = NO;
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToThirdButton:) userInfo:nil repeats:NO];
}
...
int64_t delayInSeconds = 0.6;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
do something to the button(s)
});
Less code is better code.
[NSThread sleepForTimeInterval:0.06];
Swift:
Thread.sleep(forTimeInterval: 0.06)
A slightly less verbose way is to use the performSelector: withObject: afterDelay:
which sets up the NSTimer object for you and can be easily cancelled
So continuing with the previous example this would be
[self performSelector:#selector(goToSecondButton) withObject:nil afterDelay:.06];
More info in the doc
Try
NSDate *future = [NSDate dateWithTimeIntervalSinceNow: 0.06 ];
[NSThread sleepUntilDate:future];
I would like to add a bit the answer by Avner Barr. When using int64, it appears that when we surpass the 1.0 value, the function seems to delay differently. So I think at this point, we should use NSTimeInterval.
So, the final code is:
NSTimeInterval delayInSeconds = 0.05;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//do your tasks here
});
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
Is the best one to use. Using sleep(15); will cause the user unable to perform any other actions. With the following function, you would replace goToSecondButton with the appropriate selector or command, which can also be from the frameworks.
I have a HUD panel in an app and I want to be able to take a set of images and show each image on the panel for a few seconds before displaying the next image. I'm very new to Cocoa and am having trouble implementing this so some pointers would be welcomed. Here's what I'm currently trying:
[newShots enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop)
{
//get url
NSURL *imageUrl = [[NSURL alloc] initWithString:[obj objectForKey:#"image_url"]];;
//get image from url
NSImage *image = [[NSImage alloc] initWithContentsOfURL:imageUrl];
//set it to shot panel
[shotPanelImageView setImage:image];
//clean up
[imageUrl release];
[image release];
//set needs refresh
[shotPanelImageView setNeedsDisplay:YES];
//sleep a few before moving to next
[NSThread sleepForTimeInterval:3.0];
}];
As you can see I'm just looping the info for each image, grabbing it via URL, setting it to the view, and then calling thread sleep for a few seconds before moving on. The issue is that the view will not redraw with the new image when it is assigned. I thought that setNeedsDisplay:YES would force a redraw but only the first image in the collection is ever displayed. I've put in NSLog()'s and debugged and I know for sure the enumeration is working correctly as I can see the new image information being set as it should.
Is there something I'm missing or is this a completely wrong way of going about solving this problem?
Thanks,
Craig
You are sleeping the main thread, which I'm pretty sure is not a good idea. I suggest that the Cocoa way to do what you want is to use a timer. In place of your code above:
[NSTimer scheduledTimerWithTimeInterval:3.0
target:self
selector:#selector(showNextShot:)
userInfo:nil
repeats:YES];
(The userInfo parameter allows you to pass along an arbitrary object that you want to use when the timer fires, so you could potentially use this to keep track of the current index as an NSNumber, but it would have to be wrapped in a mutable container object, because you can't set it later.)
Then put the code from your block into the method called by the timer. You'll need to make an instance variable for the current index.
- (void)showNextShot:(NSTimer *)timer {
if( currentShotIdx >= [newShots count] ){
[timer invalidate]; // Stop the timer
return; // Also call another cleanup method if needed
}
NSDictionary * obj = [newShots objectAtIndex:currentShotIdx];
// Your code...
currentShotIdx++;
}
To avoid the initial 3 sec delay caused by using a timer, you can call the same method your timer uses, right before you set it up:
[self showNextShot:nil]
[NSTimer scheduled...
Or could also schedule a non-repeating timer to fire as soon as possible (if you really wanted to use userInfo):
[NSTimer scheduledTimerWithTimeInterval:0.0
...
repeats:NO];
EDIT: I forgot about -initWithFireDate:interval:target:selector:userInfo:repeats:!
NSTimer *tim = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:3.0
target:self
selector:#selector(showNextShot:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:tim
forMode:NSDefaultRunLoopMode];
[tim release];