I have two methods that both create a random sprite node and add it to the scene. Let's call them spriteMethod1 and spriteMethod2.
I'd like to have a looping method that runs spriteMethod1, 5 times, and then spriteMethod2 once. There also needs to be a delay between each time the spriteMethods are called.
I thought the following might work, but it doesn't:
-(void) addObjects {
for (int i = 0; i < 5; i++) {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
}
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
I know this might not be the best solution, but it works for me:
-(void)addObjects {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:4];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:6];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:8];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:10];
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:13];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:18];
}
Add a timer in your interface:
#property (nonatomic, weak) NSTimer *timer;
schedule a timer somewhere in your code
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(spriteMethod1) userInfo:nil repeats:YES];
do this
int count = 0;
- (void)spriteMethod1 {
count ++;
// do your work here
if(count == 5) {
// stop the timer
[self.timer invalidate];
// call the other methods
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
}
The problem is that you won't see the delay because the selector is enqueued on the thread loop. From the documentation:
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
Another approach is to make the thread sleep for a few seconds.
[self spriteMethod1];
[NSThread sleepForTimeInterval:2.0f];
In this case your User Interface will hang if you don't execute this code in a separate thread.
This out the top of my head.. not sure if it'll do the trick:
- (void)startAddingObjects
{
NSNumber *counter = #(0);
[self performSelector:#selector(addMoreUsingCounter:)
withObject:counter
afterDelay:2];
}
- (void)addMoreUsingCounter:(NSNumber *)counter
{
int primCounter = [counter intValue];
if (primCounter < 5)
{
[self spriteMethod1];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:2];
}
else if (primCounter < 7)
{
[self spriteMethod2];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:3];
}
}
You'll probably still need to relate the delay to the counter to get the exact results you need.
I think your version doesn't work, because the "afterDelay" param is relative to the moment of invocation. You would need to multiply it with "i", in the for loop, and then use 13 and 18 respectively for the last two selectors.
Look into using an NSOperationQueue. You can set its maxConcurrentOperationCount to 1, to ensure it executes its actions sequentially. E.g.
NSOperationQueue * opQueue = [[NSOperationQueue alloc] init];
opQueue.maxConcurrentOperationCount = 1; // this ensures it'll execute the actions sequentially
NSBlockOperation *spriteMethod1Invoker = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; ++i)
{
[self spriteMethod1];
sleep(2); // sleep 2 secs between invoking spriteMethod1 again
}
}];
NSInvocationOperation *spriteMethod2Invoker = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(spriteMethod2) object:nil];
[opQueue addOperation:spriteMethod1Invoker];
[opQueue addOperation:spriteMethod2Invoker];
Related
I would like to run a concurrent NSOperation. For this reason, I have extended the NSOperation class and overridden the start and finish methods. It looks something like this:
#import "AVFrameConversionOP.h"
#implementation AVFrameConversionOP //extends NSOperation
- (void)start
{
[self willChangeValueForKey:#"isFinished"];
_isFinished = NO;
[self didChangeValueForKey:#"isFinished"];
// Begin
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
if (_isCancelled == YES) {
NSLog(#"** OPERATION CANCELED **");
[self willChangeValueForKey:#"isFinished"];
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self performTask];
[self finish];
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
NSLog(#"operationfinished, _isFinished is %d", _isFinished);
if (_isCancelled == YES)
{
NSLog(#"** OPERATION CANCELED **");
}
}
- (void)performTask
{
//The task is performed here
sleep(0.1); //This is just for demonstration purposes
}
#end
I have defined the completion block for this NSOperation in the main view controller in the viewDidLoad method:
- (void)viewDidLoad {
NSLog(#"viewDidLoad!");
[super viewDidLoad];
static int counter = 0;
_frameConvOP = [[AVFrameConversionOP alloc] init]; //Initializing the modified NSOperation class
__weak typeof(self) weakSelf = self;
_frameConvOP.completionBlock = ^ {
counter++;
//NSLogs don't seem to work here
/*if(counter>1){
exit(-1); //Never happens because counter only ever becomes =1
}*/
};
[[VideoPreviewer instance] setNSOperation:_frameConvOP]; //This is where I send the NSOperation object to the class where I'm using it
}
I'm starting the NSOperation by doing [frameConvOP start] inside of a loop. It is called continuously every time a frame is decoded, so that is to say it is called very often. I can see that the finish and start methods of the NSOperation are being called frequently through the logs. And the functions inside these methods are also being correctly called. However, the NSLogs inside the completion blocked aren't logged. I put an exit(-1) inside the completion block and the app crashed, but the NSLogs never show up. So I put a static counter in there and it only increments to 1. So the completion block is only being called once.
Can anyone explain why the completion block is being called only once? Is it because of the way I'm (incorrectly) handling the KVO for _isFinished and _isExecuting? The completion block should be called when _isFinished is set to YES and as far as I can see, I am doing that in the finish block. And the finish block is being called quite frequently as it should be.
Any help, guidance or direction is appreciated. Please let me know if I have made any errors or if I have been less-than-clear in the post.
Thanks,
From the NSOperation class reference:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
You are not allowed to invoke -start multiple times on a single operation object.
Another thing, in your -finish method, you need to properly nest the -willChange... and -didChange... calls. That is, you need to call -didChange... in the reverse order of the -willChange... calls:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
I am using animateWithDuration to change the value of a UISlider:
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [myUISlider setValue:10]; }
completion:^(BOOL finished){ }];
The problem is, I need to be able to display the current value of the UISlider while it is changing. Is this even possible using animateWithDuration? I tried creating a UISlider subclass and overriding setValue in hopes of getting access to the slider's value as it is being changed:
-(void)setValue:(float)newValue
{
[super setValue:newValue];
NSLog(#"The new value is: %f",newValue);
}
But that code only gets called at the very end of the animation block. In a way that makes perfect sense, since I really only called setValue once. But I was hoping that the animation block would somehow call it over and over again within its internal mechanisms, but that appears not to be the case.
If UIView animateWithDuration is a dead-end here, I wonder if there is a better way to acheive this same functionality with something else? Perhaps another slick little block-driven part of the SDK I don't know about which allows animating more than just UIView parameters?
I think the best way is to handle it is to code a custom Slide you can creat it like progress bar which there are many demo on github.
You can also use NSTimer to do it , but I do not think it is a very better way.
When you tap , you can creat a timer :
_timer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(setValueAnimation) userInfo:nil repeats:YES];
and set a ivar: _value = oldValue;
in the setValueAnimation method :
- (void)setValueAnimation
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
return;
}
_value += .05;
[self setVale:_value];
}
UPDATE
how to add block:
1st:you can define a block handler :
typedef void (^CompleteHandler)();
2nd: creat your block and added it into the userInfo :
CompleteHandler block = ^(){
NSLog(#"Complete");
};
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:block,#"block", nil];
3rd: make the NSTimer:
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(setValueAnimation:) userInfo:userInfo repeats:YES];
4th:achieve your timer method:
- (void)setValueAnimation:(NSTimer *)timer
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
// also can use [_delegate complete];
CompleteHandler block = [timer.userInfo objectForKey:#"block"];
if (block) {
block();
}
return;
}
_value += .05;
[self setVale:_value];
}
I do not know if there is any sort of notification that you could "tap into" to get the UIView animation "steps", but you can do the animations "manually" using an NSTimer, and that will allow you to continuously update your value.
If there's an out of the box solution using UIView - I'm all ears. This animation framework (only 2 classes!) - PRTween https://github.com/chris838/PRTween will give you access to convenience timing functions and also the changed value.
Here's an updated project of mine https://github.com/jdp-global/KACircleProgressView/
PRTweenPeriod *period = [PRTweenPeriod periodWithStartValue:0 endValue:10.0 duration:1.0];
PRTweenOperation *operation = [PRTweenOperation new];
operation.period = period;
operation.target = self;
operation.timingFunction = &PRTweenTimingFunctionLinear;
operation.updateSelector = #selector(update:)
[[PRTween sharedInstance] addTweenOperation:operation]
- (void)update:(PRTweenPeriod*)period {
[myUISlider setValue:period.tweenedValue];
}
i want to remove some objects from a NSMutableDictionary after some Time.
I have:
//first Method start
PedObjectTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fireTimer:) userInfo:PedMessage repeats:YES];
[PedMessage setObject:PedObjectTimer forKey:#"Timer"];
[PedObjects setObject:[PedMessage copy] forKey:[PedMessage objectForKey:#"ObjectID"]];
//first Method end
- (void) fireTimer:(NSTimer*)theTimer {
NSLog(#"%#",PedObjects);
[PedObjects removeObjectForKey:[[theTimer userInfo]objectForKey:#"ObjectID"]];
NSLog(#"%#",PedObjects);
[theTimer invalidate];
theTimer = nil;
}
A problem is that i cant copy the Timer Object to the dictionary...
How can i delete the right one after 2 sec in the fireTimer method?
with this i get the same result , if i have 2 selectors it will not remove 2 entries in my dictionary...
[PedObjects setObject:[PedMessage copy] forKey:[PedMessage objectForKey:#"ObjectID"]];
[self performSelector:#selector(delete:) withObject:[PedObjects objectForKey:[PedMessage objectForKey:#"ObjectID"]] afterDelay:2.0];
-(void)delete:(NSMutableDictionary*)dict
{
NSLog(#"%#",[PedMessage objectForKey:#"ObjectID"]);
NSLog(#"%#",PedObjects);
[PedObjects removeObjectForKey:[dict objectForKey:#"ObjectID"]];
NSLog(#"%#",PedObjects);
}
You could use something like this:
Instead of a timer, you call
[self performSelector:#selector(delete:) withObject:yourDictionary afterDelay:2.0];
//delete implementation
-(void)delete:(NSMutableDictionary*)dict {
[dict removeObjectForKey:#"yourKey"];
}
I have an animation which is made up of an array of images, on which I then run
[myUIImageView startAnimating]
I want the animation to run once, then stop for 3 seconds, then repeat.
I need to run this animation in a separate thread so I have
NSThread *animationThread = [[NSThread alloc] initWithTarget:self selector:#selector(startAnimTask) withObject:nil waitUntilDone:NO];
[animationThread start];
in my viewDidLoad, and then
-(void) startAnimTask {
//create array of images here; animationDuration (3) and animationRepeatCount (1)
[self setUpAnimation];
while (true){
[myUIImageView startAnimating];
usleep(3);
[myUIImageView stopAnimating];
usleep(3);
}
}
Using this method, I receive a memory warning. I've also tried running the start and stop on the MainThread with no luck.
Any ideas?
Do this:
[myUIImageView startAnimating];
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:(myUIImageView.animationDuration+3.0)];
Now selector is:
-(void)startAnimTask
{
[myUIImageView startAnimating];
//repeat again then add above selector code line here
}
UI is not thread safe, so UI calls should be executed only in main thread, maybe this is the case.
I think you can go with this:
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:0.1];
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:3.0];
In startAnimTask method write your animation logic code.
Enjoy Coding :)
Try this approach to complete your task:
[self performSelector:#selector(myTask)
withObject:nil
afterDelay:3.0];
-(void)myTask{
//Write your logic for animation
}
I have started playing with UIProgressView in iOS5, but havent really had luck with it. I am having trouble updating view. I have set of sequential actions, after each i update progress. Problem is, progress view is not updated little by little but only after all have finished. It goes something like this:
float cnt = 0.2;
for (Photo *photo in [Photo photos]) {
[photos addObject:[photo createJSON]];
[progressBar setProgress:cnt animated:YES];
cnt += 0.2;
}
Browsing stack overflow, i have found posts like these - setProgress is no longer updating UIProgressView since iOS 5, implying in order for this to work, i need to run a separate thread.
I would like to clarify this, do i really need separate thread for UIProgressView to work properly?
Yes the entire purpose of progress view is for threading.
If you're running that loop on the main thread you're blocking the UI. If you block the UI then users can interact and the UI can't update. YOu should do all heavy lifting on the background thread and update the UI on the main Thread.
Heres a little sample
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(backgroundProcess) withObject:nil];
}
- (void)backgroundProcess
{
for (int i = 0; i < 500; i++) {
// Do Work...
// Update UI
[self performSelectorOnMainThread:#selector(setLoaderProgress:) withObject:[NSNumber numberWithFloat:i/500.0] waitUntilDone:NO];
}
}
- (void)setLoaderProgress:(NSNumber *)number
{
[progressView setProgress:number.floatValue animated:YES];
}
Define UIProgressView in .h file:
IBOutlet UIProgressView *progress1;
In .m file:
test=1.0;
progress1.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
- (void)makeMyProgressBarMoving {
NSLog(#"test %f",test);
float actual = [progress1 progress];
NSLog(#"actual %f",actual);
if (progress1.progress >1.0){
progress1.progress = 0.0;
test=0.0;
}
NSLog(#"progress1.progress %f",progress1.progress);
lbl4.text=[NSString stringWithFormat:#" %i %%",(int)((progress1.progress) * 100) ] ;
progress1.progress = test/100.00;//actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
test++;
}
I had similar problem. UIProgressView was not updating although I did setProgress and even tried setNeedsDisplay, etc.
float progress = (float)currentPlaybackTime / (float)totalPlaybackTime;
[self.progressView setProgress:progress animated:YES];
I had (int) before progress in setProgress part. If you call setProgress with int, it will not update like UISlider. One should call it with float value only from 0 to 1.