I am working on a sample project in which I have a vertical scrollview and a horizontal scrollview. The vertical scrollview has a number of subviews. In scrollviewDidScroll I am performing some actions. Along with this I now want to animate a subview inside the vertical scrollview when that particular subview is visible on the screen. For this I am doing a certain calculation which is correct. The animation is as follows:
The subview contains multiple custom views. I am trying to animate(reducing and then again increasing alpha value for the view) each of these views in some particular time sequence(so that the animation looks to be in sequence). For this I am posting notification to the views and the animation sequence and logic is perfect according to what I want.
But I am facing the problem that the code is executing when I put the breakpoint, but the animation does not show up. In case if I post the notification after I stop scrolling then the animation happens perfectly fine. But I want is the animation to happen even when I am scrolling and the view is on screen.
I am adding my code snippet as below:
SubView: (Inside my scrollview)
- (void)animateSequenceTemplate {
if (!hasSequenceTemplateAnimated) {
[self performSelector:#selector(animateSequenceSlides) withObject:nil afterDelay:0];
hasSequenceTemplateAnimated = YES;
}
else {
return;
}
}
- (void)animateSequenceSlides {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:imageAsset forKey:#"assetMO"];
[[NSNotificationCenter defaultCenter]postNotificationName:AnimateSequenceSlide object:nil userInfo:userInfo];
}
Subviews inside the above subview:
- (void)animateSlideView:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
if ([userInfo objectForKey:#"assetMO"] == assetMO) {
[[NSNotificationCenter defaultCenter]removeObserver:self name:AnimateSequenceSlide object:nil];
CGFloat duration = 0.035;
float delay = (self.slideCount * duration);
CGFloat timeDelay = 0;
[self performSelector:#selector(animationPhase1) withObject:nil afterDelay:delay];
timeDelay = delay + duration;
[self performSelector:#selector(animationPhase2) withObject:nil afterDelay:timeDelay];
if (self.slideCount == (self.maxSlideCount - 1)) {
timeDelay += duration * 18; //No animation till 18 frames
}
else {
timeDelay += duration;
}
[self performSelector:#selector(animationPhase3) withObject:nil afterDelay:timeDelay];
timeDelay += duration;
[self performSelector:#selector(animationPhase4) withObject:nil afterDelay:timeDelay];
}
}
- (void)animationPhase1 {
[UIView animateWithDuration:0.035 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ {
[self setAlpha:0.85];
}completion:^(BOOL finished) {
}];
}
There occurs two scenarios inside - (void)animateSequenceTemplate:
I call as [self animateSequenceSlides];. In this case the animation does not show up.
[self performSelector:#selector(animateSequenceSlides) withObject:nil afterDelay:0.0f];. In this case the animation shows up but after the scrollview rests.
I am compelled to use the UIView animation using perform selector because if I remove this and use either nested UIView animate blocks / or directly call these methods, then again animation does not show up. Now at lease it shows up after the I rest scrolling.
I would like advice on a solution or any guess on what mistake I might be making.
It possible, that you executing it not on the main queue.
dispatch_async(dispatch_get_main_queue, block()) might help.
When working with animation, main queue the only place for executing code, that makes animation happen.
EDIT:
[self performSelector:#selector(animationPhase1) withObject:nil afterDelay:delay];
I’m not sure, where it happens, but I don't think on main thread.
Try this:
[self performSelectorOnMainThread:#selector(animationPhase1)
withObject:nil
waitUntilDone:YES];
I’m not sure about delay, that’s why I prefer GCD functions and blocks.
Finally, i got the solution to my question and it is working very fine as expected.
Changes Made:
From "animateSequenceTemplate", calling "animateSequenceSlides"
method directly
In "animateSlideView" method mentioned above, instead of using perform selector I created NSTimer object with the required delay,
repeat set to NO, and adding the timer in the current run loop
Did the same for calling all the four animationPhase methods
Code works fine as accepted
- (void)animateSequenceTemplate {
if (!hasSequenceTemplateAnimated && self.isSequenceSlideCreated) {
hasSequenceTemplateAnimated = YES;
[self animateSequenceSlides];
}
}
- (void)animateSlideView:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
if ([userInfo objectForKey:#"assetMO"] == assetMO) {
[[NSNotificationCenter defaultCenter]removeObserver:self name:AnimateSequenceSlide object:nil];
CGFloat duration = 0.035;
float delay = (self.slideCount * duration);
CGFloat timeDelay = 0;
NSTimer *animate1 = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(animationPhase1) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop]addTimer:animate1 forMode:NSRunLoopCommonModes];
timeDelay = delay + duration;
NSTimer *animate2 = [NSTimer scheduledTimerWithTimeInterval:timeDelay target:self selector:#selector(animationPhase2) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop]addTimer:animate2 forMode:NSRunLoopCommonModes];
if (self.slideCount == (self.maxSlideCount - 1)) {
timeDelay += duration * 18; //No animation till 18 frames
}
else {
timeDelay += duration;
}
NSTimer *animate3 = [NSTimer scheduledTimerWithTimeInterval:timeDelay target:self selector:#selector(animationPhase3) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop]addTimer:animate3 forMode:NSRunLoopCommonModes];
timeDelay += duration;
NSTimer *animate4 = [NSTimer scheduledTimerWithTimeInterval:timeDelay target:self selector:#selector(animationPhase4) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop]addTimer:animate4 forMode:NSRunLoopCommonModes];
}
}
I am not satisfied with the goodness of the code structure. If you think of any other smart way of doing it, please share.
Related
I implemented corePlot in my xcode project. I'm trying to "explode" a slice of the pie chart with animation. Here is the method I'm using:
- (void)radialOffsetForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)idx
{
if (myIndex == idx) {
return 20;
}
return 0;
}
I have another method which calls [pieChart reloadRadialOffset];.
For example:
- (void)thisMethod {
[pieChart reloadRadialOffset];
}
How can I animate the reloadRadialOffsets?
I just added an example to the "Simple Pie Chart" demo in the Plot Gallery example app. I added two properties to the controller to hold the index of the selected slice and the desired offset value. Since the offset is a CGFloat it is easily animated using Core Animation or CPTAnimation.
Set the index to NSNotFound to indicate that no slice should be selected. You could also use an array or set of indices if you want to highlight more than one slice at a time.
self.offsetIndex = NSNotFound;
Trigger the animation to offset the slice:
self.offsetIndex = idx;
[CPTAnimation animate:self
property:#"sliceOffset"
from:0.0
to:35.0
duration:0.5
animationCurve:CPTAnimationCurveCubicOut
delegate:nil];
The plot datasource needs the radial offset method:
-(CGFloat)radialOffsetForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)index
{
return index == self.offsetIndex ? self.sliceOffset : 0.0;
}
The sliceOffset property needs a custom setter to trigger the plot to update the offsets during the animation:
-(void)setSliceOffset:(CGFloat)newOffset
{
if ( newOffset != sliceOffset ) {
sliceOffset = newOffset;
[self.graphs[0] reloadData];
}
}
In your question is bit confusing. Your question title says "how to reaptedly call a mathod using timer" and at the end of your question it changes to "How can I animate the reloadRadialOffsets?".
For repeatedly calling a method you can use any of the following options
option 1.
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(animatedPie:)
userInfo:nil
repeats:YES];
option 2:
[self performSelector:#seletor(animatePie) withObject:nil afterDelay:1.0];
and in your method
-(void) animatePie
{
[UIView animateWithDuration:1.0 animations:^
{
[pieChart reloadRadialOffsets];
} completion:^(BOOL finished)
{
[self performSelector:#seletor(animatePie) withObject:nil afterDelay:1.0];
}];
}
Here the repeated method will be called agian with a delay once the animation is complete.
When you wanted to stop the animation call
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)arg
When timer is used it will be fired once the interval is elapsed.
For animation
You can use
[UIView animateWithDuration:1.0 animations:^
{
} completion:^(BOOL finished) {
}];
or user
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
You can use below code. In this UIViewAnimationOptionRepeat is helpful for repeat animation what you want to achieve..
[UIView animateWithDuration:5
delay:1
options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionRepeat
animations:^{
[UIView setAnimationRepeatCount:2];
[pieChart reloadRadialOffsets];
}
completion:nil];
//Still you want to call method using timer
NSTimer *animateTimer;
animateTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(thisMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:animateTimer forMode:NSDefaultRunLoopMode];
[animateTimer fire];
- (void)thisMethod {
[pieChart reloadRadialOffsets];
}
Hope it helps you...!
I am using NSTimer to move through my UIScrollview, but I can't get to make it stop at every page for like let's say a second or two so users can be able to preview and click if they want to or when they are scrolling around the images in the UIScrollview my Code is below....:
-(void) onTimer {
CGPoint rightOffset = CGPointMake(responseScroll.contentSize.width - responseScroll.bounds.size.width, 0);
[responseScroll setContentOffset:rightOffset animated:YES];
}
-(void)viewDidLoad {
/-------/
[NSTimer scheduledTimerWithTimeInterval:0.008 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
Make the timer interval 2.0:
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
This means that the timer will fire every 2 seconds.
If you want to shift one page over, you need to adjust your onTimer method:
CGPoint rightOffset = CGPointMake(responseScroll.contentOffset.x + responseScroll.frame.size.width, 0);
[responseScroll setContentOffset:rightOffset animated:YES];
Consider using UIAnimations instead
-(void)viewDidLoad{
[self moveScrollView];
}
-(void)moveScrollView{
CGPoint rightOffset = CGPointMake(responseScroll.contentOffset.x+responseScroll.bounds.size.width, 0);
if(rightOffset.x+responseScroll.bounds.size.width<=responseScroll.contentSize.width){
[UIView animateWithDuration:1 animations:^{
[responseScroll setContentOffset:rightOffset animated:NO];
} completion:^(BOOL finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self moveScrollView];
});
}];
}
}
Here is my answer, this is in swift. This will scroll the pages in scrollview infinitely.
private func startBannerSlideShow()
{
UIView.animate(withDuration: 6, delay: 0.1, options: .allowUserInteraction, animations: {
scrollviewOutlt.contentOffset.x = (scrollviewOutlt.contentOffset.x == scrollviewOutlt.bounds.width*2) ? 0 : scrollviewOutlt.contentOffset.x+scrollviewOutlt.bounds.width
}, completion: { (status) in
self.startBannerSlideShow()
})
}
I am stuck in something I hope you guys can help
I have a scrollview , when the user scrolling a subview appear with animation from bottom to top. the timer then start counting 5 sec and then call another method to hide the subview
I implemented and it works as wanted except :
while the subview appear and when it's almost to hide , if I scrolled that moment the subview appear statically and never hide . try to scrolling again another subview dynamically work over the static one ( as it duplicated or something)
this is my code for controlling show and hide of subview
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(!show){
[self showSubview];
if (!myidleTimer)
[self resetIdleTimer];
}
}
-(void)resetIdleTimer
{
//convert the wait period into minutes rather than seconds
int timeout = kApplicationTimeoutInMinutes;// equal to 5 seconds
[myidleTimer invalidate];
myidleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
}
-(void)idleTimerExceeded
{
if (show){
[myidleTimer invalidate];
[self hideSubview];
show=false;
}
}
"show" is a bool to insure when to hide and when to show
her is the show / hide implementation
-(void)hideSubview{
[UIView animateWithDuration:0.5
animations:^{
subview.frame = CGRectMake(0, screenWidth, screenHeight, 60);//move it out of screen
} completion:^(BOOL finished) {
[subview removeFromSuperview];
subview.frame=CGRectMake(0,screenWidth, screenHeight, 0);
}];
show=false;
}
-(void) showSubview{
subview = [[UIView alloc] init ];
[self.view addSubview:subview];
subview.frame = CGRectMake(0, screenWidth, screenHeight, 60);
[UIView animateWithDuration:1.0
animations:^{
subview.frame = CGRectMake(0, screenWidth-60, screenHeight, 60);
}];
show=TRUE;
}
I hope that's clear enough to understand and be able to help me identify the problem
thanks in advance
The timer will not fire while the scroll view is being scrolled, if you create the timer the way you do. Instead, create it as below.
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(doStuff:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: adds the timer to the run loop using the defaultRunLoopMode instead of the NSRunLoopCommonModes, which is the one you want to have timer fire while the user scrolls.
In my viewController I inserted a timer for a calculation of numbers taken directly from my database of Parse.com.
The timer is working correctly, were inserted in viewDidAppear and in ViewDidDisappear in such a way as to lock the sequence when changing view controller.
The problem I have is that when I change the view controller with the Push the timer does not stop you give an example:
I open the application and the calculation is done correctly with the animation of NSTimer.
Change View, and then I go back with the back button to view where the timer at this point I see that the numbers contiunano to increase every time I change view and go back ...
Can you explain where I'm wrong?
-(void)viewDidAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
[self.navigationController.navigationBar setBackgroundColor:[UIColor clearColor]];
self.title = NSLocalizedString(#"", nil);
[self animatelayertopoint:0];
[self AnimationMenuView:600];
[self QueryForViewPrincipal];
[self QueryForCollectionView];
[self ShowBadgeNumber];
[self Timer];
[collectionView reloadData];
}
-(void)Timer{
timer = [NSTimer scheduledTimerWithTimeInterval:0.012 target:self selector:#selector(CalcoloMediadiLaurea) userInfo:nil repeats:YES];
}
-(void) CalcoloMediadiLaurea{
PFUser *CurrentUser = [PFUser currentUser];
NSNumber *NumeroUndici = [NSNumber numberWithInt:11];
NSNumber *NumeroTre = [NSNumber numberWithInt:3];
NSNumber *ValoreMediaPonderata = [CurrentUser objectForKey:FF_USER_MEDIA_PONDERATA];
int FFValoreTre = [NumeroTre intValue];
int FFValoreUndici = [NumeroUndici intValue];
double MediaPonderata = [ValoreMediaPonderata doubleValue];
double RisultatoMoltiplicazioneValoriPonderataUndici;
RisultatoMoltiplicazioneValoriPonderataUndici = MediaPonderata * FFValoreUndici;
double RisultatoMediaLaurea;
RisultatoMediaLaurea = RisultatoMoltiplicazioneValoriPonderataUndici / FFValoreTre;
CalcoloMediaLaureaLabel.text = [NSString stringWithFormat:#"%.1f", FFTickValoreMediaLaurea ];
FFTickValoreMediaLaurea++;
if(FFTickValoreMediaLaurea > RisultatoMediaLaurea){
[timer invalidate];
timer = nil;
}
}
-(void)viewDidDisappear:(BOOL)animated {
[self animatelayertopoint:0];
[self AnimationMenuView:600];
[self CalcoloMediadiLaurea];
}
The timer get rescheduled again on view did appear without getting invalidated to the previous one.You should make instance of timer in .h and then hold the reference of timer in it .And on view did appear when you are scheduling the timer just invalidate it and then schedule it again.
if([self.timer isValid])
{
[self.timer invalidate];
self.timer=nil;
self.timer=[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(CalcoloMediadiLaurea:) userInfo:nil repeats:YES];
}
This worked for me which is along the lines of the above answer but a little more explicitly stated.
- (void)viewWillAppear:(BOOL)animated
{
NSTimeInterval secondsBetween = [endDate timeIntervalSinceDate:currentTime];
secondsLeft = secondsBetween;
[self countdownTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[self.timer invalidate];
self.timer = nil;
}
//Initiates timer
- (void)countdownTimer
{
[self.timer invalidate];
self.timer = nil;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
}
It's because you are creating new timers without invalidating old ones. The timers don't get invalidated automatically, you have to do it yourself. So if U go on second view and return before your invalidate condition is true you will create new timer and have two of them :)... And so on.
I have a simple explosion animation that works just fine in any method in my class. However I want to call it once the timer runs out, from the timer update method. The problem is it will not work from here for whatever reason (something to do with the timer surely). It simply unhides my image.. no animation. I cannot figure out how to get it to work.
- (void)explosionAnimation
{
imgExplosion.hidden=FALSE;
//set starting point of explosion
CGRect explosionFrame = self.imgExplosion.frame;
explosionFrame.origin.x = explosionFrame.origin.y = -960.0f;
explosionFrame.size.width = explosionFrame.size.height = 1920.0f;
[UIView animateWithDuration:1.5f animations:^{
self.imgExplosion.frame = explosionFrame;
} completion:^(BOOL finished) {
self.imgExplosion.hidden = YES;
}];
}
-(void) createTimer
{
// Create timer instance
if (timer != nil){
[timer invalidate];
hasTimerStarted = NO;
}
// Initialize the timer.
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerUpdater)
userInfo:nil repeats:YES];
}
- (void)timerUpdater{
if(!hasTimerStarted){
intTotalSeconds = [strTotalNumberofSeconds intValue];
hasTimerStarted = YES;
}
else{
intTotalSeconds--;
self.lblTimer.text = [self revertTimeToString:intTotalSeconds];
}
// if totalSeconds hits zero, then time is up! You lose!
if (intTotalSeconds == 0){
[timer invalidate];
[self explosionAnimation];
hasTimerStarted = NO;
[self addPlayAgainButton];
}
}
Try putting some delay or call explosion method on Main Thread like
[self performSelector:#Selector(explosionAnimation) withObject:nil afterDelay:1.0];