Hide one UIView and show another - ios

When I click on an a segment of my UISegmentedControl, I want one of two UIViews to be displayed. But how do I arrange it, that I only show the new view. Currently I am calling thisview.removeFromSuperview() on the old one, and then setup the new all from scratch. I also tried setting all the HeightConstants of the views subviews to zero and then set the heightConstants of the view itself to zero but I'd rather avoid that constraints-surgery..
What better approaches are there?

Agree with #rmaddy about using UIView's hidden property, a nice simple way to cause a view to not be drawn but still occupy its place in the view hierarchy and constraint system.
You can achieve a simple animation to make it a bit less jarring as follows:
UIView.animate(withDuration:0.4, animations: {
myView.alpha = 0
}) { (result: Bool) in
myView.isHidden = true
}
This will fade the alpha on the view "myView", then upon completion set it to hidden.
The same animation concept can be used also if you've views need to re-arrange themselves, animating layout changes will be a nice touch.

Based on #rmaddy and #CSmiths answer, I built the following function:
func changeView(newView: UIView, oldView: UIView) {
newView.isHidden = false
newView.alpha = 0
UIView.animate(withDuration:0.4, animations: {
oldView.alpha = 0
newView.alpha = 1
}) { (result: Bool) in
oldView.isHidden = true
}
}
I feel dumb now for all the hours I spent on that constraint-surgery. :|

Related

When implementing custom view controller presentations, where to apply presented view's constraints?

When presenting a view controller using a custom animation, none of Apple's documentation or example code mentions or includes constraints, beyond the following:
// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;
The problem is that the double-height status bar is not recognized by custom-presented view controllers (view controllers that use non-custom presentations don't have this problem). The presented view controller is owned by the transition's container view, which is a temporary view provided by UIKit that we have next to no dominion over. If we anchor the presented view to that transient container, it only works on certain OS versions; not to mention, Apple has never suggested doing this.
UPDATE 1: There is no way to consistently handle a double-height status bar with custom modal presentations. I think Apple botched it here and I suspect they will eventually phase it out.
UPDATE 2: The double-height status bar has been phased out and no longer exists on non-edge-to-edge devices.
My answer is: You should not use constraints in case of custom modal presentations
Therefore I know your pain, so I will try to help you to save time and effort by providing some hints which I suddenly revealed.
Example case:
Card UI animation like follows:
Terms for further use:
Parent - UIViewController with "Detail" bar button item
Child - UIViewController with "Another"
Troubles you mentioned began, when my animation involved size change along with the movement. It causes different kinds of effects including:
Parent's under-status-bar area appeared and disappeared
Parent's subviews were animated poorly - jumps, duplication and other glitches.
After few days of debugging and searching I came up with the following solution (sorry for some magic numbers ;)):
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0.4,
options: .curveEaseIn, animations: {
toVC.view.transform = CGAffineTransform(translationX: 0, y: self.finalFrame.minY)
toVC.view.frame = self.finalFrame
toVC.view.layer.cornerRadius = self.cornerRadius
fromVC.view.layer.cornerRadius = self.cornerRadius
var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, scale, scale, 1.0)
transform = CATransform3DTranslate(transform, 0, wdiff, 0)
fromVC.view.layer.transform = transform
fromVC.view.alpha = 0.6
}) { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
Main point here is, that You have to use CGAffineTransform3D to avoid animation problems and problems with subviews animation (2D Transforms are not working for unknown reasons).
This approach fixes, I hope, all your problems without using constraints.
Feel free to ask questions.
UPD: According to In-Call status bar
After hours of all possible experiments and examining similar projects like this and this and stackoverflow questions like this, this (it's actually fun, OPs answer is there) and similar I am totally confused. Seems like my solution handles Double status bar on UIKit level (it adjusts properly), but the same movement is ignoring previous transformations. The reason is unknown.
Code samples:
You can see the working solution here on Github
P.S. I'm not sure if it's ok to post a GitHub link in the answer. I'd appreciate for an advice how to post 100-300 lines code In the answer.
I've been struggling with double-height statusBar in my current project and I was able to solve almost every issue (the last remaining one is a very strange transformation issue when the presentingViewController is embedded inside a UITabBarController).
When the height of the status bar changes, a notification is posted.
Your UIPresentationController subclass should subscribe to that particular notification and adjust the frame of the containerView and its subviews:
UIApplication.willChangeStatusBarFrameNotification
Here is an example of code I'm using:
final class MyCustomPresentationController: UIPresentationController {
// MARK: - StatusBar
private func subscribeToStatusBarNotifications() {
let notificationName = UIApplication.willChangeStatusBarFrameNotification
NotificationCenter.default.addObserver(self, selector: #selector(statusBarWillChangeFrame(notification:)), name: notificationName, object: nil)
}
#objc private func statusBarWillChangeFrame(notification: Notification?) {
if let newFrame = notification?.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? CGRect {
statusBarWillChangeFrame(to: newFrame)
} else {
statusBarWillChangeFrame(to: .zero)
}
}
func statusBarWillChangeFrame(to newFrame: CGRect) {
layoutContainerView(animated: true)
}
// MARK: - Object Lifecycle
deinit {
// Unsubscribe from all notifications
NotificationCenter.default.removeObserver(self)
}
// MARK: - Layout
/// Called when the status-bar is about to change its frame.
/// Relayout the containerView and its subviews
private func layoutContainerView(animated: Bool) {
guard let containerView = self.containerView else { return }
// Retrieve informations about status-bar
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let normalStatusBarHeight = Constants.Number.statusBarNormalHeight // 20
let isStatusBarNormal = statusBarHeight ==~ normalStatusBarHeight
if animated {
containerView.frame = …
updatePresentedViewFrame(animated: true)
} else {
// Update containerView frame
containerView.frame = …
updatePresentedViewFrame(animated: false)
}
}
func updatePresentedViewFrame(animated: Bool) {
self.presentedView?.frame = …
}
}

Autolayouts : How to create collapse/Expand dynamic view swift 3/4

I have a problem that I can't create a view that can collapse it's contents that may be dynamic height , i know it's easy to collapse it with height constant = 0 , but i can't figure out how to make it expand again as the content of that view is dynamic and sub-itmes may be added later
I want that behavior
Your answer has massively overcomplicated a very simple solution.
You should first create your zeroHeightConstraint as a property.
let zeroHeightConstraint: NSLayoutConstraint = dynamicView.heightAnchor.constraint(equalToConstant: 0)
You could do this as an outlet from the storyboard too if you wanted.
Now add your button action...
#IBAction func toggleCollapisbleView() {
if animationRunning { return }
let shouldCollapse = dynamicView.frame.height != 0
animateView(isCollapse: shouldCollapse,
buttonText: shouldCollapse ? "Expand" : "Collapse")
}
private func animateView(isCollapse: Bool, buttonText: String) {
zeroHeightConstraint.isActive = isCollapse
animationRunning = true
UIView.animate(withDuration duration: 1, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
self.animationRunning = false
self.button.setTitle(buttonText, for: .normal)
})
}
Always avoid using view.tag in your code. It should always be seen as "code smell". There is almost always a much more elegant way of doing what you are trying to do.
Regarding the bottom constraint. You should really be using a UIStackView to layout the contents of the inside of the view here. By doing that you no longer need to iterate the subviews of your contentView you can access the UIStackView directly. And do the same thing there. In fact... a much better way would be to create the bottomConstraint against the view using a priority less than 1000. e.g. 999.
By doing that you don't have to remove or add that constraint at all. When the zeroHeightConstraint is not active it will be there and work. When the zeroHeightConstraint is active it will override the bottom constraint automatically.

Swift: Rounded corners appear different upon first appearance and reappearing

class ApplyCorners: UIButton {
override func didMoveToWindow() {
self.layer.cornerRadius = self.frame.height / 2
}
}
I apply this class to the buttons in my application and it is working great, but when I apply it to a button used in every cell in a table view the button corners are not round upon entering the view, but if I click one of the buttons I get segued to another view. If I then segue back the corners are "fixed" / round.
The green is the button when returning and the red is upon first entering the view.
Anyone know how to fix this?
I'd suggest layoutSubviews, which captures whenever the frame of the button changes:
class ApplyCorners: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height / 2
}
}
This takes care of both the original appearance and any subsequent appearance. It also avoids all sorts of problems related to not only whether the frame was known when the view appeared, but also if you do anything that might change the size of the button (e.g. anything related to constraints, rotation events, etc.).
This sort of thing is likely to be a timing problem. Consider the phrase self.frame.height. At the time didMoveToWindow is called, we may not yet know our frame. If you are going to call a method that depends upon layout, do so when layout has actually occurred.
Gonna propose another alternative: listen to any bounds changes. This avoids the problem of wondering "is my frame set yet when this is called?"
class ApplyCorners: UIButton {
override var bounds: CGRect {
didSet {
layer.cornerRadius = bounds.height / 2
}
}
}
Edited frame to bounds because as #Rob points out, listening for frame changes will cause you to miss the initial load sometimes.
Putting your code in didMoveToWindow() does not make sense to me. I'd suggest implementing layoutSubviews() instead. That method gets called any time a view object's layout changes, so it should update if you resize your view.
(Changed my suggestion based on comments from TNguyen and and Rob.)

Not all content is animated inside a stack view when hiding it

I'm currently working on a iOS (swift 3) app. I have a simple vertical stack view containing 2 horizontal stack views. In some cases I want to hide the bottom one. I do so by using the following code
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true;
};
The animation shown below doesn't really do what I would expect:
While the border of the buttons is animated properly when hiding, the text inside each button doesn't seem to be affected until the very end. Any idea as to how I could fix this?
I kept doing some research on the subject, and it seems like most articles were suggesting that using stacks to perform animation would work fine. However I have also found that animations would only work with animatable properties, isHidden not being one of them.
In the end after some trial and errors I have found that isHidden can be animated with stack views, but you can expect children to misbehave. So far the only workaround I have found is like so:
let duration = 0.5;
let delay = 0;
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.isHidden = self.hideBottomStack;
})
UIView.animate(withDuration: duration/2, delay: delay, animations: {
self.bottomStack.alpha = 0;
})
You'll note here that I basically "turn" the alpha property down to 0 in half the time I take to hide the stack. This has the effect to hide the text before it overlaps with the upper stack. Also note that I could also have decided to do something like this:
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.alpha = 0;
}, completion: { (_) in
self.bottomStack.isHidden = true;
})
This would also hide the bottom stack, but you lose the hiding motion in favor of a fading motion and hide the stack once the fading is done.
I'm not sure about this, I think stackviews can cause weird behaviour sometimes. Have you tried adding "self.view.layoutIfNeeded()" inside the UIView.animate block? Like this:
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true
self.view.layoutIfNeeded()
}
I think it should also work if you put the "self.bottomStackView.isHidden = true" above the UIView.animate, not sure though, not an expert at it.
Also, I don't think you need to use ";" after your line of code in swift :)

Swift: removefromSuperview removes constraints

Currently working on some swift program and I've a call to action where I remove a blurview from the superview and at the same time I'm animating 2 buttons.
Everything works like it should but there is one small problem. When I remove the blurview from my superview the constraints on my 2 buttons are being set to 0 on the bottom and animating from that position.
I don't want them to shift to 0. If I don't remove the blurview my animation is working perfectly. I've checked if my button constraints are related to the blurview, but that isn't the case. Because I assumed that it could only reset my constraints when they are relative to the blurview.
My storyboard looks the following:
view
|-> camera_view
|-> blur_view
|-> record_label
|-> record_button
The code that I'm executing is the following:
#IBAction func recordButton(sender: AnyObject) {
self.blurView?.removeFromSuperview()
UIButton.animateWithDuration(0.3, delay: 0.2, options: .CurveEaseOut, animations: {
var recordButtonFrame = self.recordButton.frame
var recordLabelFrame = self.recordLabel.frame
recordButtonFrame.origin.y -= recordButtonFrame.size.height
recordLabelFrame.origin.y -= recordLabelFrame.size.height
self.recordButton.frame = recordButtonFrame
self.recordLabel.frame = recordLabelFrame
}, completion: { finished in
print("Button moved")
})
}
What am I doing wrong?
Kind regards,
Wouter
Instead of removing blurView from superview you could hide it.
Replace
self.blurView?.removeFromSuperview()
with
self.blurView?.hidden = true
The problem is that you're animating frames while using constraints. You should be animating constrain changes / constraint constant value changes.
When you don't remove the view the layout isn't recalculated so your frame animation 'works'. It isn't correct and will get reorganised at some point in the future.
When you remove the view the layout is recalculated and everything moves around before your animation starts.
You don't give details of your constraints but it seems likely that you should be animating constraints before removing the view, then removing and ensuring the constraints are all sane on completion.

Resources