How to mask a ViewController when presenting a view over it - ios

I am trying to mask a ViewController when i present a view like in The way i present my view is by anchoring it outside of the screen like this:
addContactTopAnchor = addContact.bottomAnchor.constraint(equalTo:
view.topAnchor)
addContactTopAnchor.isActive = true
addContact.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
addContact.heightAnchor.constraint(equalToConstant: 182).isActive = true
and when i press a button i move it onto the display like this:
func addTapped(){
self.addContactTopAnchor.isActive = false
self.addContactTopAnchor = addContact.bottomAnchor.constraint(equalTo: view.topAnchor, constant: 182)
self.addContactTopAnchor.isActive = true
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
I want to mask everything except for the view just like its show in my screenshot.

I created a view
let mask: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = 0
return view
}()
and in my button press, I set the alpha and brought my view to the front
func addTapped() {
self.addContactTopAnchor.isActive = false
self.addContactTopAnchor = addContact.topAnchor.constraint(equalTo: view.bottomAnchor, constant: -182)
self.addContactTopAnchor.isActive = true
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.view.layoutIfNeeded()
self.mask.alpha = 0.5
self.navigationController?.navigationBar.alpha = 0.5
self.navigationController?.navigationBar.shadowImage = UIImage()
self.view.bringSubview(toFront: self.addContact)
})
}
EDIT: Also i added this to mask the navigation bar:
self.navigationController?.navigationBar.alpha = 0.5
self.navigationController?.navigationBar.shadowImage = UIImage()
thanks #desdenova for the help

Related

Transparency on a UITableView causes navigation animation to look bad

I'm using a UITableView with a Navigation Controller and I have made the former partially transparent, which looks great.
The problem I am running into is that when I press a button, the transition animation (to change to another view) looks odd because the old view that is sliding behind the new one is visible for a time.
I have tried things like temporarily shutting off transparency (either suddenly, or gradually), and while it looks a little better, overall the experience still isn't great.
I guess it might be possible to do a custom animation, but this seems like a bad idea since it will likely look different than the built-in OS animation. Actually, even with a custom animation I am not sure how I would do it since I think I would run into the same issue.
Does anyone have any ideas how I can make things look cleaner?
UPDATE: adding more detail based on questions asked in the comments
The UI is a pretty complex set of pieces but I'll try to describe the relevant parts here.
There is a UISplitViewController [A], and I have created a UIVisualEffectView (with UIBlurEffect) that is attached as a subview of A's parent. My menu consists of a UINavigationController [B], and a UITableViewController [C] that is the top level of the menu. [B] is added as a subview as the content view of the blur effect view.
Two other UITableViewControllers [D] and [E] are transitioned to when button [1] or [2] are pressed on [C].
There are a few other view controllers that are subviews of [A] (or [A]'s parent) that are showing through, blurred, but that is the design and there is no issue there.
The problem is for the transition animations from [C]->[D], [D]->[C] (via back button), [C]->[E], or [E]->[C], you can see the controller that is moving away behind the controller that is coming in. So if you do [C]->[D] (via pressing button [1] on [C]) then you will see [C] going behind [D] as it slides in, and [C] eventually disappears.
The actual showing of [D] or [E] is done via a line of code like this (inside the custom class of [C])
self.navigationController?.show(myVC, sender: self)
where navigationController is [B] and myVC is [D].
The transition back to [C] is done via popViewController().
OK - trying to (minimally) emulate your setup description...
View controller with an image view filling the entire view
Navigation controller added as a child VC
Two VCs for the nav controller...
both with transparent background
"Page 1" pushes to "Page 2"
So I assume you mean you have a current "push/pop" transition that looks like this with simulator Debug -> Slow Animations to exaggerate the effect (these are kinda "heavy" gifs, so open them in a new browser tab if the animation isn't running):
And your goal is something close or similar to this:
You will likely need to use a custom transition.
I was able to get those results using the code from this article unedited: Simple, custom navigation transitions -- note: this is not mine - just found it from quick searching.
Here's the code for the full example -- everything is done via code, no #IBOutlet or #IBAction connections needed. Just assign a new view controller's custom class as NavSubVC :
class NavSubVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
guard let img = UIImage(named: "navBKG") else {
DispatchQueue.main.async {
let a = UIAlertController(title: "Alert", message: "Could not load \"navBKG\" image", preferredStyle: .alert)
self.present(a, animated: true, completion: nil)
}
return
}
let imgView = UIImageView(image: img)
imgView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imgView)
let rvc = Page1VC()
let navC = UINavigationController(rootViewController: rvc)
self.addChild(navC)
guard let navView = navC.view else { return }
view.addSubview(navView)
navC.didMove(toParent: self)
navView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
navView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
navView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
navView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
navView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
// let's have a gray nav bar always showing
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithTransparentBackground()
navigationBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationBarAppearance.backgroundColor = .systemGray
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().compactAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
// let's add a border to the navigation controller view
// so we can see its frame (since the controllers have clear backgrounds)
navView.layer.borderWidth = 2
navView.layer.borderColor = UIColor.yellow.cgColor
// un-comment this line to see the custom transition
//navC.addCustomTransitioning()
}
}
class PageBaseVC: UIViewController {
var labels: [UILabel] = []
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
for i in 1...6 {
let v = UILabel()
v.text = "\(i)"
v.textAlignment = .center
v.textColor = .white
v.translatesAutoresizingMaskIntoConstraints = false
v.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
v.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
labels.append(v)
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
labels[0].topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
labels[0].leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
labels[1].centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
labels[1].leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
labels[2].bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
labels[2].leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
labels[3].topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
labels[3].trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
labels[4].centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
labels[4].trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
labels[5].bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
labels[5].trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
])
}
}
class Page1VC: PageBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Page 1"
labels.forEach { v in
v.backgroundColor = .systemBlue
}
let b = UIButton()
b.backgroundColor = .systemGreen
b.setTitle("Push to Page 2", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(b)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
b.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
b.centerXAnchor.constraint(equalTo: g.centerXAnchor),
b.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
b.heightAnchor.constraint(equalToConstant: 60.0),
])
b.addTarget(self, action: #selector(doPush(_:)), for: .touchUpInside)
}
#objc func doPush(_ sender: Any?) {
let vc = Page2VC()
self.navigationController?.pushViewController(vc, animated: true)
}
}
class Page2VC: PageBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Page 2"
labels.forEach { v in
v.backgroundColor = .systemRed
}
}
}
// Custom Navigation Transition
// from: https://ordinarycoding.com/articles/simple-custom-uinavigationcontroller-transitions/
final class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
// 1
let presenting: Bool
// 2
init(presenting: Bool) {
self.presenting = presenting
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
// 3
return TimeInterval(UINavigationController.hideShowBarDuration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 4
guard let fromView = transitionContext.view(forKey: .from) else { return }
guard let toView = transitionContext.view(forKey: .to) else { return }
// 5
let duration = transitionDuration(using: transitionContext)
// 6
let container = transitionContext.containerView
if presenting {
container.addSubview(toView)
} else {
container.insertSubview(toView, belowSubview: fromView)
}
// 7
let toViewFrame = toView.frame
toView.frame = CGRect(x: presenting ? toView.frame.width : -toView.frame.width, y: toView.frame.origin.y, width: toView.frame.width, height: toView.frame.height)
let animations = {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5) {
toView.alpha = 1
if self.presenting {
fromView.alpha = 0
}
}
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1) {
toView.frame = toViewFrame
fromView.frame = CGRect(x: self.presenting ? -fromView.frame.width : fromView.frame.width, y: fromView.frame.origin.y, width: fromView.frame.width, height: fromView.frame.height)
if !self.presenting {
fromView.alpha = 0
}
}
}
UIView.animateKeyframes(withDuration: duration,
delay: 0,
options: .calculationModeCubic,
animations: animations,
completion: { finished in
// 8
container.addSubview(toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
final class TransitionCoordinator: NSObject, UINavigationControllerDelegate {
// 1
var interactionController: UIPercentDrivenInteractiveTransition?
// 2
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return TransitionAnimator(presenting: true)
case .pop:
return TransitionAnimator(presenting: false)
default:
return nil
}
}
// 3
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactionController
}
}
extension UINavigationController {
// 1
static private var coordinatorHelperKey = "UINavigationController.TransitionCoordinatorHelper"
// 2
var transitionCoordinatorHelper: TransitionCoordinator? {
return objc_getAssociatedObject(self, &UINavigationController.coordinatorHelperKey) as? TransitionCoordinator
}
func addCustomTransitioning() {
// 3
var object = objc_getAssociatedObject(self, &UINavigationController.coordinatorHelperKey)
guard object == nil else {
return
}
object = TransitionCoordinator()
let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &UINavigationController.coordinatorHelperKey, object, nonatomic)
// 4
delegate = object as? TransitionCoordinator
// 5
let edgeSwipeGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
edgeSwipeGestureRecognizer.edges = .left
view.addGestureRecognizer(edgeSwipeGestureRecognizer)
}
// 6
#objc func handleSwipe(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
guard let gestureRecognizerView = gestureRecognizer.view else {
transitionCoordinatorHelper?.interactionController = nil
return
}
let percent = gestureRecognizer.translation(in: gestureRecognizerView).x / gestureRecognizerView.bounds.size.width
if gestureRecognizer.state == .began {
transitionCoordinatorHelper?.interactionController = UIPercentDrivenInteractiveTransition()
popViewController(animated: true)
} else if gestureRecognizer.state == .changed {
transitionCoordinatorHelper?.interactionController?.update(percent)
} else if gestureRecognizer.state == .ended {
if percent > 0.5 && gestureRecognizer.state != .cancelled {
transitionCoordinatorHelper?.interactionController?.finish()
} else {
transitionCoordinatorHelper?.interactionController?.cancel()
}
transitionCoordinatorHelper?.interactionController = nil
}
}
}

How to get rounded corner & dynamic height of popped custom modalPresentationStyle VC

When present a VC, the default style is not cover full screen and with a rounded corner like below gif illustrated.
But I want to control the height of modalPresentation, let's say a 1/4 screen height by default and dynamic changed according to tableView's rows of popped VC. So I implement a custom modalPresentationStyle on the base VC wit below code.
However, I found these issues after:
The popped VC is not rounded corner but with rectangle corner.
I cannot drag to move the popped VC anymore, it is in fixed position.
It would be more better if I could to increment the popped VC's height per its tableView rows count. Not a must item.
#objc func collectButtonTapped(_ sender: Any?) {
let vc = PlayListViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
present(vc, animated: true)
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
class HalfSizePresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
guard let bounds = containerView?.bounds else { return .zero }
return CGRect(x: 0, y: bounds.height * 0.75, width: bounds.width, height: bounds.height * 0.75)
}
}
Try to assign cornerRadius to your vc:
#objc func collectButtonTapped(_ sender: Any?) {
let vc = PlayListViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
// assign corner radius
vc.view.layer.cornerRadius = 20
vc.view.clipsToBounds = true
vc.view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] // this is for corner radius only for top
present(vc, animated: true)
}
for total control of vc presented position, you can use child vc and auto layout, for present child vc (like a modal presentation style) you can use UIView.animate on child vc top constraint.
this is an example of child vc and auto layout:
import UIKit
class YourController: UIViewController {
private lazy var firstChildVc = AiutiFirst()
let myButton: UIButton = {
let b = UIButton(type: .system)
b.layer.cornerRadius = 10
b.clipsToBounds = true
b.backgroundColor = .black
b.setTitleColor(.white, for: .normal)
b.setTitle("Present", for: .normal)
b.addTarget(self, action: #selector(handlePresent), for: .touchUpInside)
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
addChildVC()
}
var up = false
#objc fileprivate func handlePresent() {
print("present")
if up == false {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
self.menuDown?.isActive = false
self.menuUp?.isActive = true
self.myButton.setTitle("Dismiss", for: .normal)
self.view.layoutIfNeeded()
} completion: { _ in
print("Animation completed")
self.up = true
}
} else {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
self.menuUp?.isActive = false
self.menuDown?.isActive = true
self.myButton.setTitle("Present", for: .normal)
self.view.layoutIfNeeded()
} completion: { _ in
print("Animation completed")
self.up = false
}
}
}
var menuUp: NSLayoutConstraint?
var menuDown: NSLayoutConstraint?
fileprivate func addChildVC() {
addChild(firstChildVc)
firstChildVc.view.translatesAutoresizingMaskIntoConstraints = false
firstChildVc.view.layer.cornerRadius = 20
firstChildVc.view.clipsToBounds = true
firstChildVc.view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] // this is for corner radius only for top
view.addSubview(firstChildVc.view)
menuUp = firstChildVc.view.topAnchor.constraint(equalTo: view.centerYAnchor)
menuDown = firstChildVc.view.topAnchor.constraint(equalTo: view.bottomAnchor)
menuDown?.isActive = true
firstChildVc.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
firstChildVc.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
firstChildVc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
firstChildVc.didMove(toParent: self)
view.addSubview(myButton)
myButton.bottomAnchor.constraint(equalTo: view.centerYAnchor, constant: -40).isActive = true
myButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
myButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
}
And this is the result:
To animate child vc presentation you can use UIView.animate function for top child vc constraint, or Pan gesture do drag it, or whatever you deem necessary and valid to use...
to show it full screen simple set child vc top anchor to top of intere view:
menuUp = firstChildVc.view.topAnchor.constraint(equalTo: view.topAnchor)

Swift - addTarget to UIButton inside a Subview

I have a problem with my UIButton. Just for basic understanding:
User taps on a button -> popUpView appears (UIView)
User taps on UIButton which is a SubView of popUpView -> popUpView dismisses
That is my code for that:
#objc func addWishButtonTapped(notification : Notification){
view.addSubview(popUpView)
popUpView.addSubview(popUpTextField)
popUpView.addSubview(wishButton)
// constrain popUpView
popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -100).isActive = true
popUpView.heightAnchor.constraint(equalToConstant: 200).isActive = true
popUpView.widthAnchor.constraint(equalToConstant: view.frame.width - 85).isActive = true
// constrain wishButton
wishButton.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
wishButton.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: 130).isActive = true
wishButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
// constrain textField
popUpTextField.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
popUpTextField.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: -50).isActive = true
popUpTextField.heightAnchor.constraint(equalToConstant: 40).isActive = true
popUpTextField.widthAnchor.constraint(equalToConstant: view.frame.width - 170).isActive = true
popUpView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
popUpView.alpha = 0
UIView.animate(withDuration: 0.3) {
self.visualEffectView.alpha = 1
self.popUpView.alpha = 1
self.popUpView.transform = CGAffineTransform.identity
}
// make whishButton clickable
self.wishButton.addTarget(self, action: #selector(wishButtonTapped), for: .touchUpInside)
}
#objc func wishButtonTapped(){
print("test")
insertWhish()
dismissPopUpView()
}
Problem:
whishButton is not clickable and I have no idea why.. I'm stuck on this for a while now so I am grateful for any help, thanks :)
UPDATE
This is my View Hierarchy:
Really weird, because the selected UIImage should actually be the UIButton which is in my case behind it (100x100 square). I definitely declared my whishButton as an actual UIButton()
first, try whit this line
self.popUpView.bringSubviewToFront(whishButton)
and if its necessary try this line
self.popUpView.sendSubview(yourImageBackGround)
Try to move buttons in storyboard
SOLVED
I solved my problem.. not in a very smooth way but it's working now. I just added the button as another subview of my normal UIView instead of adding it within my popUpView.
I guess, you don't create any instantiate of your root class globally.
class ViewController: UIViewController {
var viewController: MyViewController
override func viewDidLoad() {
super.viewDidLoad()
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
myViewController =storyBoard.instantiateViewController(withIdentifier:"MyViewController") as! MyViewController
self.present(myViewController, animated:true, completion:nil)
myViewController.addWishButtonTapped(nil)
}
}
class MyViewController: UIViewController {
let popUpView = UIView()
let popUpTextField = UITextField()
let wishButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func addWishButtonTapped(notification : Notification?){
view.addSubview(popUpView)
popUpView.addSubview(popUpTextField)
popUpView.addSubview(wishButton)
// constrain popUpView
popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -100).isActive = true
popUpView.heightAnchor.constraint(equalToConstant: 200).isActive = true
popUpView.widthAnchor.constraint(equalToConstant: view.frame.width - 85).isActive = true
// constrain wishButton
wishButton.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
wishButton.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: 130).isActive = true
wishButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
// constrain textField
popUpTextField.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
popUpTextField.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: -50).isActive = true
popUpTextField.heightAnchor.constraint(equalToConstant: 40).isActive = true
popUpTextField.widthAnchor.constraint(equalToConstant: view.frame.width - 170).isActive = true
popUpView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
popUpView.alpha = 0
UIView.animate(withDuration: 0.3) {
self.visualEffectView.alpha = 1
self.popUpView.alpha = 1
self.popUpView.transform = CGAffineTransform.identity
}
// make whishButton clickable
self.wishButton.addTarget(self, action: #selector(wishButtonTapped), for: .touchUpInside)
}
#objc func wishButtonTapped(){
print("test")
insertWhish()
dismissPopUpView()
}
}

Fold and unfold animation of UIImageView using constraints

As you can see in my code bellow, I'm trying to make a fold/unfold animation using constraints. Certainly the gray background has the fold/unfold animation but the image itself doesn't.
How can I get same fold/unfold effect of the image itself?
class ViewController2: UIViewController {
var folded: Bool = false
var imagen: UIImageView!
private var foldConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let imagen = UIImageView(contentMode: .scaleAspectFill, image: #imageLiteral(resourceName: "gpointbutton"))
imagen.translatesAutoresizingMaskIntoConstraints = false
imagen.backgroundColor = .gray
view.addSubview(imagen)
self.imagen = imagen
imagen.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imagen.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 0)
createAnimationButton()
}
private func createAnimationButton() {
let button = UIButton(title: "Animate", titleColor: .blue)
button.translatesAutoresizingMaskIntoConstraints = false
button.addAction(for: .touchUpInside) { [weak self] (_) in
guard let self = self else { return }
self.folded = !self.folded
if self.folded {
self.foldConstraint.isActive = true
UIView.animate(withDuration: 0.5) {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}
} else {
self.foldConstraint.isActive = false
UIView.animate(withDuration: 0.5) {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}
}
}
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}
One thing to note here is that the width or height constraint is set to 0 (accurately also includes 0.1), and the same is hidden.
Then you need to set the height constraint to be greater than 0.1
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 0)
Replace with this, temporarily set to 1
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 1)
Hide it at the end of the animation
self.folded = !self.folded
if self.folded {
self.foldConstraint.isActive = true
UIView.animate(withDuration: 1, animations: {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}) { (completion) in
self.imagen.isHidden = true
}
} else {
self.imagen.isHidden = false
self.foldConstraint.isActive = false
UIView.animate(withDuration: 1, animations: {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
})
}
Update:
scaleAspectFill is not suitable for animation, it should be set to scaleAspectFit
let imagen = UIImageView(contentMode: .scaleAspectFit, image: #imageLiteral(resourceName: "gpointbutton"))

How to add a view as subview for certain controllers

I have multiple storyboards in my app. I want to add a view on always on the top just below the navigation bar for some of the controllers. How Can I achieve this?
I already used navigation delegate and add a view in the window but no luck. Steps to show the gray view in the attached image is.
1. On click of a button on that view controller; a gray view should show and remain on the top of the controllers until all the scanning of the device is not done whether the user should go any of the viewControllers.
You can create a UINavigationController subclass and add the view in it.
class NavigationController: UINavigationController {
let customView = UIView()
let iconImgView = UIImageView()
let msgLbl = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
customView.isHidden = true
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = .gray
view.addSubview(customView)
iconImgView.contentMode = .scaleAspectFit
iconImgView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(iconImgView)
msgLbl.numberOfLines = 0
msgLbl.lineBreakMode = .byWordWrapping
msgLbl.textColor = .white
msgLbl.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(msgLbl)
customView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
iconImgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.heightAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.centerYAnchor.constraint(equalTo: customView.centerYAnchor).isActive = true
iconImgView.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 15).isActive = true
iconImgView.trailingAnchor.constraint(equalTo: msgLbl.leadingAnchor, constant: 15).isActive = true
msgLbl.topAnchor.constraint(equalTo: customView.topAnchor, constant: 10).isActive = true
msgLbl.bottomAnchor.constraint(equalTo: customView.bottomAnchor, constant: 10).isActive = true
msgLbl.trailingAnchor.constraint(equalTo: customView.trailingAnchor, constant: -15).isActive = true
msgLbl.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true
}
func showCustomView(message: String, icon: UIImage) {
msgLbl.text = message
iconImgView.image = icon
customView.isHidden = false
}
func hideCustomView() {
customView.isHidden = true
}
}
Embed all your view controllers in this navigation controller. When you want to show/hide the gray view in a view controller use
Show
(self.navigationController as? NavigationController)?.showCustomView(message: "Any Message", icon: UIImage(named: "anyImage")!)
Hide
(self.navigationController as? NavigationController)?.hideCustomView()
When you push another view controller from the same navigation controller the view won't be hidden until you call the hide method
You can simply create a custom UIView with the relevant frame and call addSubview() on the view you want to add it to.
lazy var customView: UIView = {
let customView = UIView(frame: CGRect.init(x: 0, y: self.view.safeAreaInsets.top, width: UIScreen.main.bounds.width, height: 100))
customView.backgroundColor = .gray
return customView
}()
#IBAction func onTapButton(_ sender: UIButton) {
self.view.addSubview(customView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.customView.removeFromSuperview()
}
To add it below the navigationBar, use y position of frame as self.view.safeAreaInsets.top. With this your customView will always be aligned below the navigationBar.
You can create the view with the height as per your requirement. I've used height = 100.
Give the correct frame and you can add any view as a subView to another view.

Resources