UIView Animations not playing sometimes (going to end state immediately) - ios

In my app I have a camera and another view controller that I present when the photo is taken. It seems that when I move the camera too fast when I take the photo, the animations stop working (even the segues back to the original UITabBar, that precedes the camera). I am not sure if this is related to CPU usage and there not seems to be a pattern here. Sometimes, only taking pictures make it behave this way. My animations are mainly this:
UIView.animateWithDuration(0.8, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
//buttons.alpha = 1 or 0
//buttonA.center.x += 100
}, completion: nil)
in ViewDidLoad there are only a few alpha change values inside animation. Most of the other animations blocks are when buttons are clicked. The most interesting thing though is that if I dismiss the view controller and come back to the camera, sometimes the behavior persists and sometimes not, like as if there is no pattern at all. When the behavior persists, it affects even the segues out of the camera view controller itself ( and the other parts of the tabBar that are the main view of the app - like in Instagram, for instance). Thus, it affects the whole app. What do you guys think would be the cause of this odd behavior?
Objective-C code is welcome as well.

You shouldn't call animate code in the ViewDidLoad: portion of the view controller lifecycle. At this point the view has finished loading but it may or may not have appeared on screen yet. There's viewWillAppear: which you can insert your animation in and at that point, when the view has appeared, it will perform the animation.

Did you try viewDidApear? I'm not sure that the view has actually been added to the view hierarchy in either viewDidLoad or viewWillAppear. The only downside is you can't assume it will only be called once in the viewController's life cycle.

Related

Change value for animation in animateTransition using UIPercentDrivenInteractiveTransition

I am trying to implement swipe up/down to dismiss view controller and everything is working great. If I start swiping up, it will finish the animation in the upwards direction and vice versa.
The problem appears when the user first swipes up but then decides to swipe the view controller down - how do I change the value in the animation to dismiss view controller to the bottom rather than to the top (set by the user's first swipe).
In my UIViewControllerAnimatedTransitioning controller I define the animation like this:
func animateTransition(transitionContext:UIViewControllerContextTransitioning) {
UIView.animateWithDuration(duration, animations: {
guard let transitionDelegate = self.transitionDelegate else {return}
snapshot.frame.origin.y = transitionDelegate.shouldAnimateUp ? -snapshot.frame.height : snapshot.frame.height
TransitionDelegate points to UIPercentDrivenInteractiveTransition controller (where the swipe gesture is defined). ShouldAnimateUp is defined in UIPanGestureRecognizer function like this:
shouldAnimateUp = translatedView.center.y < translatedView.frame.height / 2
That is if the view is in upper half, shouldAnimateUp = true and the other way around.
But unfortunately, when I call finishInteractiveTransition() func it uses the value which was initially set in UIView.animateWithDuration in UIViewControllerAnimatedTransitioning controller when dismissViewControllerAnimated(true, completion: nil) was called.
So, is there any way to change values for animation in UIViewControllerAnimatedTransitioning controller after dismissViewControllerAnimated(true, completion: nil) is called?
PS: I kind of struggled to define my problem in words (words are hard 🙄 ), so please tell me if you need additional info or if I should try to rewrite my explanation. Also, an additional hint: I would like the animation to work like image dismissal works in official Twitter app.
OK, I see what they're doing. It's actually more complicated that "if you swipe down and then up, it animates scene off upward". If you swipe down and back up, the Twitter app will cancel if you don't get much past where you started, but will go up if you pass where you started by some considerable portion (and are still swiping in that upward).
I must confess that I'm not crazy about this UX, because two very similar gestures can result in very different behavior. If you swipe down, keep your finger down, and then flick back upward, there is a seemingly arbitrary nature as to whether it's interpreted as a cancelation of the downward swipe or as an upward swipe. I personally think that if the user initiates a demonstrable downward gesture, that dragging back up should merely be a cancelation of the transition. But that's not the question here.
Anyway, if this is what you want to do, there are a couple of ways to achieve it:
You could consider using view property animator-based animations, which handle mid-flight changes to the animation more elegantly than older animation techniques. So, you theoretically could just addAnimations to your UIViewPropertyAnimator. But it seems messy to me.
See Advances in UIKit Animations and Transitions for more information about view property animators and how to use them with interruptible custom transitions.
When dismissing, you might not use UIViewControllerAnimatedTransitioning at all. Just animate the dismissal yourself.
For example, when you present, the UIPresentationController subclass can keep the presenting view (by returning false from should​Remove​Presenters​View). Then, when the gesture recognizer starts, it might not initiate a custom, interactive custom transition with dismiss at all, but instead merely adjust the frame of the presented scene (and modify the opacity of the dimming chrome). Then, at the end of the gesture, complete the animation manually and then the completion block can dismiss the presented view controller with no animation at all (because you've animated it yourself).
IMHO, this is architecturally inelegant. A view controller has no business mucking about with chrome that should be owned by the presentation controller. But, it works.
We should recognize that they might not have "presented" the full screen image at all. For example, they could have done simple view controller containment, but just added the child view controller scene to the existing scene. Then, the gesture could do whatever frame and dimming layer opacity changes it wanted, and when its done, just do the final animation and in the completion block, just remove the child view controller (e.g. willMove and removeFromParentViewController).
If I were to do this, I'd probably lean towards option 3, though I'd wager that it might not feel very satisfying for you, having invested time in custom interactive transitions already. Regardless, these are a couple of approaches you could consider.

Custom Modal Transition Dismiss Animation Runs Desynchronized

I've been working to implement a custom modal transition that uses a UIPresentationController subclass to create and manipulate an additional view during the presentation and dismissal. Apple helpfully provides an example of how to do this in the documentation, but I've hit a snag.
When presenting the modal, my custom view animations work perfectly, but when I dismiss the modal, the animations applied to the custom views in dismissalTransitionWillBegin play out of sync with the animations specified by the transition animator object I'm returning from animationControllerForDismissedController:. Specifically, the custom view's animations are ignoring the duration of the transition animation and are always playing very quickly (the duration appears to be around 0.2 seconds).
What could cause animateAlongsideTransition:completion: to ignore the duration of the base animation?
The source of the trouble appears to be a bug in iOS.
No matter how I refactored or simplified my animation code, I always ended up with the same result, so I started to wonder if there might be something in the way my project was set up that was causing the problem. I dropped my custom modal transition code into a clean project and, lo and behold, it worked perfectly on the first try.
Bit by bit I customized my test app to more closely match my real app and I was eventually able to get the problem to reappear. Through trial and error I found the combination of factors that was triggering the problem:
The presenting view controller is within a UINavigationController
The presenting view controller's bar button items include an image-based UIBarButtonItem
The window has a tint color set
When those three conditions are met, the animation block of the animateAlongsideTransition: call in dismissalTransitionWillBegin will be performed before the animation block of the animateWithDuration: call in animateTransition. This seems to prevent the custom view's animations from getting the transition animation's duration. In my testing, the animateAlongsideTransition: animations ran with a duration of 0.215 seconds, which I believe is the default duration.
I have been unable to find any way prevent the issue from occurring other than removing one of the three factors triggering it. The workaround I ultimately settled on was removing the window's tint color and instead setting a global tint color using UIView's appearance proxy. There are some side-effects--like UIAlertViews' buttons getting tinted--, but for my purposes this was an acceptable trade-off.

Full-window view in front of a collection view

I'm trying to implement the following behavior:
Long press on a collection view brings a full-window view (call it LetterView) to the front
Subsequent gestures/touches are only processed by the LetterView.
(edit: I should mention that I want a transparency effect of seeing the collectionview items beneath LetterView)
I seem to be running into behavior that everyone else is trying to implement, though - my touches get processed by both the LetterView and the collection view. I.e. I can scroll the collection view AND have hits processed by my topmost view. Showing the view hierarchy in XCode clearly shows LetterView at the front, and both the UICollectionView and the LetterView are subviews of UICollectionWrapperView.
LetterView is a UIView subclass with a UIViewController subclass. It's added to the view hierarchy programmatically, inside my UICollectionViewController subclasses's viewDidLoad method, like so:
super.viewDidLoad()
letterDrawingViewController = LetterDrawingViewController()
let viewFrame : CGRect = self.collectionView!.frame
letterDrawingViewController.view = LetterDrawingView.init(frame:viewFrame)
letterDrawingView = letterDrawingViewController.view
self.addChildViewController(letterDrawingViewController)
letterDrawingViewController.didMoveToParentViewController(self)
collectionView?.addSubview(letterDrawingView)
It doesn't appear to be a first responder issue, as I tried overriding canBecomeFirstResponder in LetterView and assigning it first responder status when I move it to the front
I tried setting userInteractionEnabled=FALSE on the CollectionView, but keeping it true on the LetterView after I moved LetterView to the front. This disabled all touch events for both views
I tried setting exclusiveTouch=True for LetterView when I moved it to the front. This didn't appear to do anything.
Aside from any specific tips, are there any general techniques for debugging hit-testing like this? According to the docs on hit-testing in iOS, iOS should prefer the "deepest" subview that returns yes for hitTest:withEvent:, which, since LetterView is a subview of collectionview, and in front of all it's cells, should be the front? Is there any logging I can enable to see a hit test over the view hierarchy in action?
Thank you!
Nate.
If letterView is full screen, you probably don't want to add it as a subview of the collection view like you are. Maybe try adding it to the application's window instead and see how that does. At least in that instance it should intercept all the touch events.
Another method, although admittedly a more fragile feeling one, would be to enable and disable user interaction on the collectionView as you present and dismiss letterView.
So, when letterView is about to be presented, you can call
self.collectionView.userInteractionEnabled = NO;
and if you also know when that view is about to be dismissed you can call
self.collectionView.userInteractionEnabled = YES;
The only thing here to worry about is that you don't get into a bad state where your letterView is not presenting and your collectionView is also ignoring a user's touch. That will feel totally broken.
Whilst I think you can deal with your issue somewhat easily I think you are making a design mistake. It feels like you are trying to code this thinking like a web developper by adding a child view to your view and trying to intercept the touches there like one would do in a modern JavaScript single page app. In iOS I think this is bad design. You should segue or present the new viewController using the methods provided by apple.
So your code should look soothing like:
letterDrawingViewController = LetterDrawingViewController()
self.presentViewController(letterDrawingViewController, animated: true, completion: nil)
iOS8 has the added benefit of allowing you to have awesome custom transitions. Take a look at this : http://www.appcoda.com/custom-segue-animations/

touchesBegan delay after presenting UIView

I'm trying to load a UIView and then right away detect touches on that new view. Currently, overriding touchesBegan gives a delay of around a second.
So, I load up the UIView and immediately keep tapping on the screen. It takes around a second for touchesBegan to be called. From that point on, all is well. However, I can't afford the ~second wait initially.
I have stripped back all the code in the UIView to just the barebones incase anything was clogging up the main thread, but the delay still persists.
How can I go about getting immediate feedback of touches from a newly presented UIView? Thanks.
-- EDIT BELOW --
I've been playing around with this for the past few hours. Even when creating a custom UIWindow and overriding sendEvent, the UITouchPhase gets halted when the new view is displayed. To begin receiving events again I have to take my finger off the screen and place it back on the screen. (I don't want to have to do this).
The problem seems to lie with the segue to the new view controller. When it segues, the touch phase is ended. If I simply add a subview to the current view controller, I see the desired functionality (i.e. instant responding to touch).
Given my newly presented view contains a lot of logic, I wanted to wrap it all up in it's own view controller rather than add it to the presenter view controller. Is there a way for me to do this and use 'addSubview` to present it? This should hopefully achieve the desired effect.
In the end, I created a custom view controller with it's own xib. Where I would have segued, I now instantiate that custom view controller and append it's view. This has eliminated the touch lag.
Have you disabled multi-touch? There's an inherent delay while the controller waits to see if there's a follow up touch (on all single touches). The initial sluggishness might be from loading up the multi-touch code and deciding what to do about it.
myViewController.view.multipleTouchEnabled=NO;
As to your final question, look into view controller containment. Since iOS 5 Apple has provided the hooks officially and safely to present one view controller as a sub view of another.
Sadly I've no insight as to the greater issue.
I found an answer that worked for me from a similar question asked here:
iOS: Why touchesBegan has some delay in some specific area in UIView
This solution isn't check-marked on that thread, so I'll copy it here to make it easier to find.
override func viewDidAppear(animated: Bool) {
let window = view.window!
let gr0 = window.gestureRecognizers![0] as UIGestureRecognizer
let gr1 = window.gestureRecognizers![1] as UIGestureRecognizer
gr0.delaysTouchesBegan = false
gr1.delaysTouchesBegan = false
}

Error: UIView's window is not equal to another view's window

I am a beginner programmer learning swift. First post here.
Extra info but maybe unnecessary
In this app I created, the user chooses an image on the main View controller and passes it to the second view controller. There the image is broken up into pieces and those pieces are placed in separate UIImageViews. The objective is to put them in the right order by swapping images.
Everything is working fine and it runs ok in the simulator. However, I am trying to add basic animations (moving the UIImageViews) but they are not performing. I know I have the correct syntax for the animation because I tested the code in another project.
Main question
When I navigate from my main view controller to second view controller, an error immediately appears in the console. Here's what it says:
2014-09-04 17:51:33.489 TileGame[79951:95150647] UIView: 0x7f7fe9c84600; frame = (0 0; 320 568); autoresize = W+H; layer = CALayer: 0x7f7fe9c848d0>>'s window is not equal to TileGame.GameScreen: 0x7f7fe9dc6bc0>'s view's window!
I can't figure out what it means but it does not seem to be causing any problems except for impeding the animation. Any ideas??
Looks like this person had a similar error message but maybe more complicated than my app. Modal viewcontroller UI not responsive after presentViewController:animated:completion:
Here's my whole project on GitHub: https://github.com/pakalewis/Parker-Lewis-CF/tree/master/TileGame
Thanks
You seem to have some fundamental misunderstandings of iOS programming paradigms. You have segues attached to your buttons, but you also then call performSegue in code. When you have a segue attached to a control, you don't need any code (and shouldn't have any) to cause the segue to execute. You also shouldn't go back to a previous view controller with a segue, other than an unwind segue; you're not really going back, you're creating a new instance of the controller you think you're going back to. This will cause a build up of controllers (as none will be deallocated) until your app runs out of memory.
So, you should delete the function, letsPlayButton: from MainScreen, and also get rid of it in the storyboard (the segue attached to that button is all you need).
Delete the segue you have going "back" from GameScreen to MainScreen, and change the code in backToMainScreen to this,
#IBAction func backToMainScreen(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}

Resources