The problem: my custom UIView animation shows only if I add the view in viewWillAppear or later.
The context: in my custom UIView I create and add the animation to the layer in my custom init function.
override init(color: UIColor) {
backgroundPulsatingDotView = RouteIndicatorDotView(color: color)
super.init(colour: color)
configureBackgroundPulsatingDot()
addPulsatingAnimation()
}
This custom UIView is deep inside the view hierarchy and ideally my view controller should not care to trigger the animation on its own or trigger the adding of view in viewWillAppear just because of this animation [besides other bugs which appear if using viewWillAppear or later stages for adding subviews]
Is there a way to still keep the logic of adding and starting the animation inside my custom view without needing a trigger from an external party which would say "hey now the view is visible, please animate yourself"?
Or where should I add the adding and starting of the animation inside my custom view lifecycle to make it work correctly with view controller life cycle?
Is there a way to still keep the logic of adding and starting the animation inside my custom view without needing a trigger from an external party which would say "hey now the view is visible, please animate yourself"?
There is nothing to animate until the view is part of the app's view hierarchy and has been initially drawn. You can't animate until then. That is why you are sent events marking the view's life stages, so that you can put your code in the right method which will run at the the right time in the view’s like cycle. init is the wrong method. It is too early.
Try overriding another lifetime method — perhaps something like layoutSubviews (though beware: this can be called many times, so you'll need to use a Bool flag so that you start the animation only the first time).
Also note that even then you can't add the dot and start animating it all in one move. After adding it, you must wait for the redraw moment to pass before you start animating it. What I usually do is step out to main thread with a very short delay so that my animation code runs after the current CATransaction has committed.
Related
I am a total noob developing iOS applications and I'm working in one right now.
I have set up my storyboard with a HomeViewController (main) and inside this, a container view, with another view controller embeded. This child view controller is outside the bounds of the view, and when a click a button in my HomeViewController it gets animated to the top of my app, showing this child with a cool animation. Everything is right.
In my child view controller I only have 3 buttons, and when they are displayed on the screen with this little animation, they are not usable. They don't trigger the action of the button being pressed nor they get "highlighted" with iOS style. Nothing happens.
I have been searching a lot about this, I have tried addChildViewController() on parent, didMove(toParentViewController: UIViewController) on child and I also have added it programmatically. When added programmatically it works OK but the view does not respect the animation, just being displayed in the screen without following the parent's container, which indeed moves with the animation.
What should I do next?
Thank you very much
Edit: My question does not seem to be a duplicate of UIView animations canceling any touch input
I'm using Spring library for the animations. Also, in that thread they are talking about user interaction being blocked while animation isn't finished. That is not my case. Animation is perfectly finished when trying the button interaction.
From my experience, there are two issues which cause this behavior most often:
Some of your container views have userInteractionEnabled set to false, or
Your container view frame is small and you're attempting to tap a button outside the frame of the container which won't work. Consider it like a window to the button and there's an invisible wall blocking touches.
Both of these are best debugged using the Xcode Debug View Hierarchy, or you can also try the Reveal app and see what's going on there.
Finally, this misbehavior was because the wrapping view in my container was smaller than the container, but as these two were out of screen I didn't realise at first. Making the wrapping view big enough to fit the child container was the solution.
Sorry because at last this was a stupid question.
Thank you all
If you use auto layout. You may check out your constraint is correct or not. Especially for biggerOrEqual and smallerOrEqual constraint still need a concrete constraint. eg:
width = 100#750
width <= superView.width#999
You should not forget the first constriant.
I implemented my own custom animation using the UIViewControllerAnimatedTransitioning protocol. However, this replaces the entire default push animation (including the navigation bar animation, and the new view sliding on top of the old one). I want to keep that stuff, and animate a simple UIView on top, which visualises how an item from the first view is being taken to the next.
Is there a way to accomplish this?
There is an "annimateAlongSide" function .... think it belongs to the TransitionCoordinator. Check that out.
I need to have my view invisible once the view appears. I do make modifications to its layer's anchor points and its frame. I found that adding the code in the viewWillLayoutSubviews will override the changes as I found it gets called multiple times. I am currently placing the frame and anchor point changes inside the viewWillAppear but sometimes you can see the view change size at the beginning. So is there a method that gets called after viewWillLayoutSubviews and before the view appears and is only called once so I could perform my changes. Or if you have other suggestions as to how to approach this i'd be open to hear.
To hide a view it is simple :
view.hidden = true
OR
view.alpha = 0.0 //hidden, value 1.0 is totally visible
In viewDidLoad
you are sure you will not see the view to hiding (already hidden before view is visible).
Avoid viewWillLayoutSubviews
it could occurs, for example, any time a view needs layout : view.layoutIfNeeded()
My view hierachy looks like this: (I use Autolayout)
ContentView
CustomView (implements drawRect)
Button
TableView
In the drawRect of the CustomView I draw a UIBezierPath from the top to the bottom through all CustomView.SuperView.subViews where the line stops if a Frame is in the way and begins to draw further till the next frame and so on.
On Startup when the DrawRect is called the Views are not finished with layouting I guess, because the frames I get from the views are not correct.
The Problem is solved if I call
customView.setNeedsLayout()
customView.layoutIfNeeded()
in the ViewDidAppear method. But this impacts performance cause it gets called twice.
What's the right way to do this?
Your approach is a violation of the principle tell, don't ask. You should be telling your view where to draw; it shouldn't be asking.
Inside CustomView:
Get rid of any calls to self.superview.
Add a property that represents the information you need to draw (like #property NSArray *framesToSkip)
In drawRect: look in your property for the information you need.
In your view controller:
In viewWillLayoutSubviews:, update your custom view's framesToSkip property with the appropriate views.
If needed, call setNeedsDisplay on your custom view.
A few general rules that, if broken, indicate you might be violating tell, don't ask:
Never access a view's superview.
Never access a view controller's parentViewController or presentingViewController.
Never import a view controller class unless it's your child / presented view controller.
I need some kind of hook or template method to override which gets called when a view is added as subview to another view, but couldn't find it in the documentation. It must be something that gets called automatically by UIKit. The reason is that my view must start some animations as soon as there is a superview, but stop animating as soon as there is no superview anymore.
I can't override -setSuperview: as Xcode is not indicating that such class exists - and I can't call super.
You want to override - (void)didMoveToSuperview.
didMoveToSuperview
Tells the view that its superview changed.
The default implementation of this method does nothing. Subclasses can override it to perform
additional actions whenever the superview changes.