I am using self.navigationController?.setNavigationBarHidden(true, animated: true)
I wonder where I can see how that animation is built in code?
I want to mimic its behavior with another custom UIView
The animation is just a slide up, so an animation is created that changes the y-origin of the view being animated.
If using auto layout, you should have a top constraint that specifies that the view's top y position is equal to the superview's top y position. You can reference these NSLayoutConstraints using #IBOutlet like you can with other storyboard elements, so in your view controller:
class MyViewController {
#IBOutlet var viewToAnimate: UIView!
#IBOutlet var topConstraint: NSLayoutConstraint!
}
You will notice that NSLayoutConstraint has a constant property, which just specifies a value to add to the second constraint attribute when calculating the resulting layout frames using that constraint. So in the case of the topConstraint, you want to add -viewToAnimate.bounds.height*, so that the bottom of the view sits just out of sight at the top of the superview.
You animate changes using the UIView class method animateWithDuration(_:animations:) - any animatable UIView properties that are changed inside the animation closure will be animated over the specified duration. But when you change your constraint's constant property, the view properties aren't changed until another layout pass is performed on the view. You can invalidate the layout, and let the layout happen in the next pass implicitly with view.setNeedsLayout(), but we need the layout pass to happen inside the view's animation block for the animation to work. So instead, you can use layoutIfNeeded() to force the subviews to layout immediately.
Put together, your animate method might look something like this:
class MyViewController {
// ...
func animateViewUp() {
UIView.animateWithDuration(0.3) {
self.topConstraint.constant = -self.viewToAnimate.bounds.height
self.view.layoutIfNeeded()
}
}
}
In reality, you would likely specify a method that allows you to toggle between a shown/hidden state, but the above code should get you started.
*N.B. Check which way around the constraint is specified! If the constraint is specified in terms of your superview, then a negative constant will move the subview downwards!
Short answer: You can't. The source for Apple's frameworks is not provided. However, most of their animations are built on top of the same Core Animation framework that us third party developers use.
What is the effect this creates? I'm not sure I've ever animated away the navigation bar on a view controller that's in place. Can you post a GIF of the animation? I can probably give you an idea of how it's done if I know what it looks like.
Related
I am trying to add a UIView on top of the messagesCollectionView, but the collectionview seems to take up the whole screen making the UIView I want to add unable to be seen. I am looking at their documentation here: https://github.com/MessageKit/MessageKit/blob/master/Sources/Layout/MessagesCollectionViewFlowLayout.swift
My thoughts are to change something in the Layout delegate but I am unsure what and where to change... any thoughts on this would be appreciated!
Thanks!
If you want to have some sort of floating view above the messagesCollectionView, then you can just add it as a subview to the MessagesViewControler's view, just make sure to do it after you've called super.viewDidLoad() because that's where MessageKit adds the collectionView as a subview so if you add it before then, then your view will be behind the collectionView and won't appear. To prevent the cells overlapping with your view, you can use messagesCollectionView.contentInset property to add padding to either the top or bottom if your view is floating there, so that the user can still scroll to all of the messages. Something like this:
override func viewDidLoad() {
super.viewDidLoad()
let subview = UIView()
view.addSubview(subview)
// Either add constraints or set the frame manually
// ...
// Set the contentInset if you want to prevent the messages from overlapping your view
messagesCollectionView.contentInset.top = 10 // For example, if your view was stickied to the top and was height 10px
}
Another route you could go is to have a parent view controller where you add the MessagesViewController as a child VC to the parent, and then size and layout the messagesCollectionView how you want. You can see an example of this in the MessageKit example app, in the MessageContainerController class: https://github.com/MessageKit/MessageKit/blob/master/Example/Sources/View%20Controllers/MessageContainerController.swift
For example, I use this code to change the frame position of a UIView called Propo:
self.Propo.frame.origin.x -= self.view.frame.width
(Propo has constraints in Storyboard)
But When the app view disappears and reappears, the UIView resets itself to its original position. How can I do to resolve it? I want the UIView to keep its position when I change it programmatically.
I've tried updateViewConstraints() by anything else append...
Constraints and manual frame manipulation are directly in conflict. You have to choose one or the other. You should instead "promote" the constraint you wish to change by Ctrl-dragging it to your view controller as an #IBOutlet in Storyboard. Then, you can manipulate its constant:
class MyViewController: UIViewController {
#IBOutlet weak var myPropoLeftEdgeConstraint: NSLayoutConstraint!
#IBAction doChangePropo(sender: AnyObject) {
myPropoLeftEdgeConstraint.constant -= view.frame.width
view.layoutIfNeeded()
}
}
This is already covered in Objective C in many places, so just do a search on programmatically changing an NSLayoutConstraint in code if you can read those examples.
How to edit constraint in code
(Edit to make more applicable: generally constraints should be animated for a clean user experience. Otherwise the eye is confused.)
You can easily animate the constraint adjustment by wrapping view.layoutIfneeded in a UIView.animateWithDuration(duration) { ... } block.
I'm using Storyboard with autolayout. In my ViewController I have a basic structure: one UIScrollView and one contentView (UIView) that contains different labels). Then I have different elements that I add in the viewDidAppear method of the class inside my contentView , then in order to recalculate it's frame size I do this:
- (void)fixContentViewHeight
{
_contentView.frame = CGRectMake(_contentView.frame.origin.x, _contentView.frame.origin.y, _contentView.frame.size.width, operators.frame.origin.y + operators.frame.size.height);
//constraint for contentView height
_contentViewHeight.constant = operators.frame.origin.y + operators.frame.size.height;
}
- (void)fixScrollViewHeight
{
_scrollView.frame = _contentView.frame;
_scrollView.contentSize = _contentView.frame.size;
}
where operators is the LAST placed element of contentView, it's always on the bottom of the frame. I call these 2 methods inside the viewDidAppear and they make the view scrollable, the problem is that the frame of contentView doesn't get updated so the last elements are always unclickable (because they're placed outside the frame of the view).
What am I doing wrong? Why the scrollView becomes scrollable but the contentView keeps it's old frame?
If you have autolayout constraints affecting the height of _contentView You will not be able to change its height by setting the frame, as the constraints will override that.
You will need to (and should be) adding new / modifying constraints in your code when you are adding new elements. then calling
_contentView.layoutIfNeeded()
After all the updates to let the UI update if it needs to, which it should.
Please update UI On Main Queue
dispatch_async(dispatch_get_main_queue(), ^{
// update any UI on Main queue
});
I think that all your frame related information should be inside viewWillLayoutSubviews() and viewDidLayoutSubviews(). You can add stuff in viewDidLoad() but for any frame management I would use mentioned methods.
In your particular situation use former, viewDidLayoutSubviews(). However, you can resolve these kind of issues using constraints connected to the code as #Simon McLoughlin mentioned. 'scrollView becomes unscrollable' means that you should update contentSize as well so keep an eye on that as well.
Try to add vertical space constraint between scroll view and your last placed element.
I have used autolayout constraints from storyboard. However in some cases, I want to calculate dynamic height of subview. I code this in viewDidAppear(), it works fine because this method is called after all view frames are set by layout constraints. The problem here is that I can see the frame set by constraints for half a second. And then the code reframes the view.
I came to know about viewDidLayout() which is called after constraints has set the frame so I can change. But it doesn't work. It is like this method is called before constraints are used.
The viewDidAppear method is called at the end of the view life cycle. So if you change the constraints here, it will always be visible.
If the change you want to do is a one time event, then you may do so in the method viewWillAppear. But this won't always help because the view drawing may not always be finished by this time. So a better option is to place the code in viewWillLayoutSubviews or viewDidLayoutSubviews.
NOTE: viewWillLayoutSubviews or viewDidLayoutSubviews will be called multiple times in a view's life cycle, such as orientation change or moving in and out of another view or any frame change for that matter. If the code change you want to put here is a one time event, then please make sure to use flags.
eg:-
- (void)viewWillLayoutSubviews {
if(firstTime) {
// put your constraint change code here.
}
}
Hope this helps! :)
As name suggests, viewDidLayoutSubviews is called when the view of your viewController has just finished its laying out and you can assume that at that dynamic height your views are in their correct places/frames according to your autolayout constraints.
Swift :
override func viewDidLayoutSubviews() {
// Set your constraint here
}
Objective C :
-(void)viewDidLayoutSubviews {
// Set your constraint here
}
I am not sure what you are actually trying to do in viewDidLayoutSubviews(). I always use to customise view by modifying layout constraint values overriding this method.
// Called to notify the view controller that its view has just laid out its subviews
override func viewDidLayoutSubviews() {
//Write your code here, any constraint modification etc.
super.viewDidLayoutSubviews()
}
I want to change a UITableViewCell's subview when it is being setup in the cellForRowAtIndexPath with the following code:
cellSubView.center = CGPointZero
Printing out the frame's coordinates shows, that the frame updates successfully, however the view is still displayed in the position that was given in the interface builder.
Overriding the viewDidAppear function with the following code would resolve this issue:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
}
But this will result in blinking: the table view already did appear when I change one of its cells' subview's position.
How is it possible to change the frame before the view did appear?
If you used autoLayout check if it's creating the constraints automatically.
If yes, then you can change the constants of these constraints instead.
If you are not using autoLayout, call layoutIfNeeded and see if that changes anything.
My bet though is on autoLayout creating constraints automatically.