UITapGestureRecognizer not updating UIView until completion - ios

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).

Related

Is it possible to animate views frame by frame continuously in iOS?

So I am working on implementing a custom view on iOS which will animate with momentum. This is something like the "wheel of fortune" wheel, where the user can flick it, and it will spin around with momentum, and eventually slow down and snap into place.
So I would like to have something like a "tick" function to update the view every frame (pseudocode):
// Function called on each animation frame
func tickAnimation(deltaTime: TimeInterval) {
myView.rotateBy(deltaTime)
...
}
So the naiive way to do this would be to just have a timer:
let timer = Timer.scheduledTimer(withTimeInterval: 1.0/60, repeats: true) { timer in
tickAnimation(deltaTime: 1.0/60)
}
But is there a better way to do this, where I can hook directly into the iOS animation system and synchronize my updates with the screen refresh directly?
I.e. is there something similar to Window.requestAnimationFrame() which you have on web?
I found something exactly you want but there is no short answer for your question hence I drop a wheel of fortune link.
If you want something shorter than you should look for UIview.animate()
you can set time of duration, delay and animation style such as curveEaseIn, curveEaseOut etc. It could be helpful for your question.

How do I refresh a circular animation from the background? Swift

My app have circle animation from the framework connected to the timer. When the timer is running, the animation starts. If the app goes into the background, the animation stops. When we return to the app, the animation continues from the same place, when did stopped. How do I get the animation to recalculate and start from where the timer is now? I attached screenshot. All animation methods are concentrated in one file
I created a new method in the framework, where I pass the remaining time. The method changes the value in the timer. Thanks guys
public func updateTime(time: Int) {
elapsedTime = TimeInterval(time)
delegate?.timerDidResume?(sender: self)
}

iOS layer color change

For my custom UIView I've overriden touchesBegan method. What I told it to do is to change its' layer's background color:
dispatch_async(dispatch_get_main_queue()){
self.layer.backgroundColor = clr_someCGColor
}
It acts weird. If I quickly tap the view while in Landscape it does everything perfectly well, but if I do this in Portrait, I have to hold it for some time to see the result, however the touchesEnded method is called right away, if I quickly tap. What could be the reason, causing the delay in Portrait?
Remove the dispatch_async wrapper. All it does is cause a delay (we can't execute on the main thread until, as you rightly say, the tap ends and touchesEnded has come and gone). You are already on the main thread, in touchesBegan, so there is no need for this extra delay.
Even better, use a tap gesture recognizer.

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.

UIScrollView animation problems

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.

Resources