UIScrollView animation problems - ios

I'm using a horizontal UIScrollView that displays photos on the first half of the screen. (Imagine a CGRect (0,0,320,120). The first scrollView is embedded in a second scrollview, which takes all the screen. When I scroll down the page (thus the second scrollView), the first scrollview stop being animated. I programmed a NSTimer to change photo every 3 seconds in the first UIScrollView, but while I'm scrolling the second scrollView, it seems like the animations are being queued. When I release my finger of the screen, there are few photo transitions (i.e: 2 changes if I scrolled without stoping for 6 seconds). In short: how can I make use of blocks (or something else) to continue my animations while I'm scrolling the second scrollView?
My NSTimer (in viewDidLoad):
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(changePage:) userInfo:nil repeats:YES];

Your iOS app has a run loop that continually checks for things like user input. If it is busy detecting touch events, things that are scheduled on that run loop will not get performed. (Similarly, if you block the main thread with e.g. a big server request, you won't get touch events during that time).
Here's how you can get the NSTimer on the right run loop:
First, use timerWithTimeInterval:target:selector:userInfo:repeats: to create a timer not scheduled on a run loop.
Second, use - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode to schedule the timer on a run loop. For the mode argument (the run loop type), use NSRunLoopCommonModes to allow the timer to fire without having to wait on the main run loop.
Note that this same effect exists for e.g. performSelectorAfterDelay if done in the main run loop. It also applies to animation completion blocks. I'm not sure if there's an easy way around the animation completion block problem besides using CoreAnimation.

Related

UITapGestureRecognizer not updating UIView until completion

I am running swift 4 on Xcode 9.4.1. My program uses a UITapGestureRecognizer for single taps with the code below:
let screenTapOnce = UITapGestureRecognizer(target: self, action: #selector(singleTapGesture))
screenTapOnce.numberOfTapsRequired = 1
view.addGestureRecognizer(screenTapOnce)
The singletap function is:
#objc func singleTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
This function has a lot of code since it is the main way to interface with the game. My problem is that any change this function makes to the UIView, (even starting a simple activity indicator) does not display until after the singleTapGesture function completes. This is a problem as I would like to show some progress during a special screen update that take 2 seconds to calculate.
Any help to understand how to force an UIView update or even understand why UITapGestureRecognizer seems to block any updates would be appreciated.
My problem is that any change this function makes to the UIView, (even starting a simple activity indicator) does not display until after the singleTapGesture function completes.
That is how iOS works. Drawing takes place on the CATransaction commit, which starts when all your code has run to completion.
This is a problem as I would like to show some progress during a special screen update that take 2 seconds to calculate
Then you need to get off the main thread and onto a background thread. If you take two seconds to calculate something on the main thread, the Watchdog process will kill your app dead before the user's very eyes. There are straightforward ways to display an indication of progress (on the main thread) while you are doing time-consuming work (on a background thread).

iOS - limit on UIView animate?

So I'm making a simple trivia game and I have a timerView that shrinks as time passes. When the user selects an answer, it needs to stop shrinking immediately - it must be very responsive. I give the user 10 seconds per question. Originally I would animate 10 times (with a duration of 1.0f), calling the next "segment" of animation in the completion block of the previous animation. In the completion block I would check to see if the user has tapped an answer, and if so I don't continue the chain. That solution works fine except that it's not very responsive because it's on a per second basis-- user taps an answer at the start of the second segment and the bar has a noticeable continuation.
My solution to THAT problem was to instead have 1000 animation calls with a duration of 0.01f. After doing that, the responsiveness was on point - the view stops animating as soon as I tap an answer -- the issue though, is that it's not actually 10 seconds, it takes more like 20.
So question number 1: what's the smallest time interval animateWithDuration can actually process properly?
Question number 2: is there a better way to accomplish what I'm trying to do accomplish?
ill answer question two: yes there definitely is a better way, have a look at CADisplayLink
use it to shrink your view a little bit each frame, and end the display link when you need to
the most responsive way is: the user taps an answer, you response in the touch callback, remove animations. you can remove animations by CALayer's removeAllAnimations method
Another way to do it is to set the view to shrinking using a single animation with linear timing, and then set the speed of the view's layer to 0 to pause the animation. When you set the speed on the layer to 0 the animation pauses instantly.
This works because under the covers, UIView animation actually creates and installs CAAnimation objects on the view's layers. It's possible to pause and continue an in-flight UIView animation just like you can a CAAnimation.
I have a project called KeyframeViewAnimations (link) on github that allows you to pause, continue, or "scrub" UIView and CAAnimations back and forth with a slider. You could use that technique. The tricky bit will be figuring out how far along the animation is.

App stops receiving NSTimer selector callbacks during any UI scrolling operation

I have an OpenGL animation drawing on the main window fired by an NSTimer. If I bring up a popover window with a scrollable UITableView menu, the animation freezes while scrolling is in process. Once the scrolling animation stops, the timer callbacks start again. Its only when the user actively tries to scroll that the main window stops updating.
It seems like Apple's scroll animation is somehow blocking dispatch on the main loop. Is this true and is there a way to fix it?
I dont really want to introduce multithreading if I can help it because that will exponentially increase the complexity of my code.
Also I tried using CADisplayLink instead of NSTimer and the display link calls are also blocked by the scrolling animation.
As answered by bdesham, "Appleā€™s #1 priority is UI responsiveness, and so in situations like this, UI actions will block other parts of your code. You need to move your timer to another thread."
NSTimer are tied to a run loop and mode. During event tracking, the mode is UITrackingRunLoopMode. By default, timers created by scheduledTimerWithTimeInterval are added with NSDefaultRunLoopMode and so do not fire during tracking.
[[NSRunLoop currentRunLoop] addTimer:yourTimer forMode:UITrackingRunLoopMode];

forcing setNeedsDisplay

I have a routine that draws a gearwheel using CoreGraphics and drawRect. I used a button to advanced the gears through the stages:
-(IBAction)advanceButtonPressed:(id)sender{
stage=stage+1;
if (stage==4) stage=0;
[self setNeedsDisplay];
}
The routine for drawing the gear is in drawRect, and the gear teeth are drawn in a new position depending on the vale of stage, which is passed to drawRect. In four steps, one per button press, the gear turns to its new position (one segment around the circle.)
I changed the buttonPressed routine to have it do the four stages in a loop, so I only need to press the button once, but it doesn't work:
-(IBAction)buttonPressed:(id)sender{
for (stage=0;stage<4;stage++){
NSLog(#"%i",stage);
[self setNeedsDisplay];
}
}
My NSLog shows that stage goes through its four values, just as it would in the earlier routine. But setNeedsDisplay is only executed once, (as a NSLog trace in drawRect confirms) so the gear is displayed in its final position but not the intermediate stages. It looks like setNeedsDisplay only executes when the button routine finishes, despite it being inside the loop.
Is there any way to force it to execute?
The iPhone only updates the screen 60 times a second no matter how often you call setNeedsDisplay. Your loop is going to execute in less than 1/60th of a second. That's why you only will see the last state.
If you want to show all four states through one button press then you could use a timer or performSelector: afterDelay to show all four states. A little more complicated but there you go.
Invoking setNeedsDisplay just marks the view to be redrawn at the next drawing cycle.
To redraw it during your method, you could explicitly call drawRect:

Here's The CA Animation Sequencing Sitch

I asked a question (Animation Sometimes Doesn't Occur) a while ago about an animation not, apparently, happening sometimes. I got no helpful answers. So, after much research and playing around, I discovered that sometimes the animation that is supposed to happen second, sometimes happens first. The other one does happen, just not when it is expected to happen. The two animations are "flip" and "disappear." As an example, there are four tiles. When one tile is touched, it flips. When a second tile is touched it is supposed to flip. If the tiles match, both tiles are disappeared. If they are not matched, the tiles flip back. The flipback animation has always occurred correctly, so far as I have been able to tell. In other words - the first tile is flipped. The second tile is flipped. The tiles do not match. The two tiles flipback. As for when the tiles match, the first tile flips correctly. Sometimes the second tile flips correctly and then they both disappear (animation group of rotate and shrink). Sometimes the second tile does not flip correctly, but they both disappear before the second tile does its flip. This is not acceptable.
This is what I have tried so far - I have tried NSThread pausing. That didn't work because the whole thread was paused, which didn't let the animation that hadn't occurred occur. I looked into NSTimer - but the method that is being called has two parameters which means I have to do NSInvocation and I haven't figured that out yet - and not sure it would work. Right now, I have put in a flag that marks when flip animations are completed. If this method eventually works, I have to figure out how to account for flipping individual tiles over and back again without upping the completion count. Anyway - using this method, I need to figure out how to tell my second animation to wait until the first animation is completed before running.
What I would ideally like to be able to do is to tell the disappear animation to always wait until the second tile is completely flipped before committing.
What I can't figure out is a) why sometimes the animations happen in the correct order, sometimes they don't and b) when the animations are trying to not happen in the correct order, how to force them to occur in the correct order.
Not really sure what code would be helpful for you to see. Ask and I shall provide!
Here is the code that calls the animations:
-(void)buttonPressed: (UIButton*)buttoni
{
[self flipTiles:(UIButton *)buttoni];
[self twoTilesFlipped];
}
Clearly, the flipTiles animation is called first. What I don't understand is why it sometimes isn't called until after the code that is called in twoTilesFlipped. I thought that programming was about logic and this just doesn't make logical sense to me. Oh, I just had a thought...I am going to put in a NSTimer delay in this code - maybe that will do the trick!
That seems to have done the trick. There are some tweaks that I obviously need to fix now, but at least it works everytime.
This is what I added:
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(twoTilesFlipped)
userInfo:nil
repeats:NO];
so the final code for the method was:
-(void)buttonPressed: (UIButton*)buttoni
{
[self flipTiles:(UIButton *)buttoni];
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(twoTilesFlipped)
userInfo:nil
repeats:NO];
}

Resources