Animating UITextView cause text disappeared while animation - ios

I am trying to animate UITextView by changing transform. But it makes text disappered while animating. Here is my test code.
class ViewController: UIViewController {
let textView = UITextView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 300).isActive = true
textView.backgroundColor = .systemOrange
textView.text = "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"
textView.textColor = .white
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
self.textView.transform = CGAffineTransform(translationX: 0, y: 1000)
})
animator.startAnimation()
}
}
}
I tried with storyboard but the result was same. Same result when I use UIView.animate method.
Why does this happen? I want to animate it with text on.
UPDATE
let contraint = textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
contraint.isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
contraint.constant = 1000
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
self.view.layoutIfNeeded()
})
animator.startAnimation()
}
I tested with this code, and got same result.

I'm afraid you'll have to animate a snapshot view instead:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let v = self.textView.snapshotView(afterScreenUpdates: true)!
v.frame = self.textView.frame
self.view.addSubview(v)
self.textView.isHidden = true
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
v.frame.origin.y = 1000
})
animator.startAnimation()
}
I don't think that's a bad solution; this is what snapshot views are for, after all.

Related

UIView.animateKeyFrames blocks Uibutton click - view hierarchy issue or nsLayoutConstraint?

I currently have a ToastView class that creates a view that contains a button, label, and image. The goal is to get this ToastView instance to slide up and down on the controller as a subview, like a notification bar. In LayoutOnController(), I add the bar below the visible part of the page using nslayoutconstraint(), using the margins of the superview passed in. Finally, I animate upwards using keyframes and transform the view upwards.
My problem is that once I animate the bar upwards, the button becomes non-interactive. I can click my button and trigger my #objc function if I only call LayoutOnController().
Im assuming my problem is either in the ToastViewController where I add the ToastView object as a subview (maybe misunderstanding my view hierarchy?), or that the NSLayoutConstraints do not behave well with UIView.AnimateKeyFrames. I have tried using layout constants
( self.bottom.constant = 50) instead of self?.transform = CGAffineTransform(translationX: 0, y: -40) , but the view doesnt show at all if I do that. Ive been stuck for a while so any insight is appreciated!
Here is my code:
import UIKit
import Alamofire
import CoreGraphics
class ToastView: UIView {
let toastSuperviewMargins: UILayoutGuide
let toastRenderType: String
var top = NSLayoutConstraint()
var bottom = NSLayoutConstraint()
var width = NSLayoutConstraint()
var height = NSLayoutConstraint()
var trailing = NSLayoutConstraint()
var leading = NSLayoutConstraint()
private let label: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 15)
label.textColor = UIColor.white
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let dismissButton: UIButton = {
let dismissButton = UIButton( type: .custom)
dismissButton.isUserInteractionEnabled = true
dismissButton.setTitleColor( UIColor.white, for: .normal)
dismissButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
dismissButton.translatesAutoresizingMaskIntoConstraints = false
return dismissButton
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
init(toastRenderType: String, toastController: UIViewController, frame: CGRect) {
self.toastSuperviewMargins = toastController.view.layoutMarginsGuide
self.toastRenderType = toastRenderType
super.init(frame: frame)
configureToastType()
layoutToastConstraints()
}
//animate upwards from bottom of screen position (configured in layoutOnController() )
//CANNOT CLICK BUTTON HERE
func animateToast(){
UIView.animateKeyframes(withDuration: 4.6, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.065, animations: { [weak self] in
self?.transform = CGAffineTransform(translationX: 0, y: -40)
})
UIView.addKeyframe(withRelativeStartTime: 0.93, relativeDuration: 0.065, animations: { [weak self] in
self?.transform = CGAffineTransform(translationX: 0, y: 50)
})
}) { (completed) in
self.removeFromSuperview()
}
}
//configure internal text and color scheme
func configureToastType(){
if self.toastRenderType == "Ok" {
self.backgroundColor = UIColor(hue: 0.4222, saturation: 0.6, brightness: 0.78, alpha: 1.0)
label.text = "Configuration saved!"
dismissButton.setTitle("OK", for: .normal)
imageView.image = UIImage(named: "checkmark.png")
}
else{
self.backgroundColor = UIColor(red: 0.87, green: 0.28, blue: 0.44, alpha: 1.00)
label.text = "Configuration deleted."
dismissButton.setTitle("Undo", for: .normal)
dismissButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
imageView.image = UIImage(named: "close2x.png")
}
dismissButton.addTarget(self, action: #selector(clickedToast), for: .touchUpInside)
}
//layout widget on the controller,using margins passed in via controller. start widget on bottom of screen.
func layoutOnController(){
let margins = self.toastSuperviewMargins
self.top = self.topAnchor.constraint(equalTo: margins.bottomAnchor, constant: 0)
self.width = self.widthAnchor.constraint(equalTo: margins.widthAnchor, multiplier: 0.92)
self.height = self.heightAnchor.constraint(equalToConstant: 48)
self.leading = self.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 16)
self.trailing = self.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 16)
NSLayoutConstraint.activate([
self.top,
self.width,
self.height,
self.leading,
self.trailing
])
}
//layout parameters internal to widget as subviews
func layoutToastConstraints(){
self.translatesAutoresizingMaskIntoConstraints = false
layer.masksToBounds = true
layer.cornerRadius = 8
addSubview(label)
addSubview(imageView)
addSubview(dismissButton)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 48),
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -69),
label.heightAnchor.constraint(equalToConstant: 20),
label.widthAnchor.constraint(equalToConstant: 226),
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
label.bottomAnchor.constraint(equalTo: self.topAnchor, constant: -14),
imageView.heightAnchor.constraint(equalToConstant: 20),
imageView.widthAnchor.constraint(equalToConstant: 20),
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 27),
imageView.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -10.33),
imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20.5),
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20.17),
dismissButton.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 24),
dismissButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
dismissButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 16),
dismissButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16),
])
self.layoutIfNeeded()
}
#objc func clickedToast(){
print("you clicked the toast button")
self.removeFromSuperview()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ToastViewController, where I am testing the bar animation:
import UIKit
import CoreGraphics
class ToastViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.backgroundColor = .white
toastTest()
}
//the toastRenderType specifies whether the notif bar is red or green
func toastTest() -> () {
let toastView = ToastView(toastRenderType: "Ok", toastController: self, frame: .zero)
view.addSubview(toastView)
toastView.layoutOnController() //button click works
toastView.animateToast() //button click ignored
}
}
You cannot tap the button because you've transformed the view. So the button is still below the bottom - only its visual representation is visible.
You can either implement hitTest(...), calculate if the touch location is inside the transformed button, and call clickedToast() if so, or...
What I would recommend:
func animateToast(){
// call this async on the main thread
// so auto-layout has time to set self's initial position
DispatchQueue.main.async { [weak self] in
guard let self = self, let sv = self.superview else { return }
// decrement the top constant by self's height + 8 (for a little spacing below)
self.top.constant -= (self.frame.height + 8)
UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: {
sv.layoutIfNeeded()
}, completion: { b in
if b {
// animate back down after 4-seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0, execute: { [weak self] in
// we only want to execute this if the button was not tapped
guard let self = self, let sv = self.superview else { return }
// set the top constant back to where it was
self.top.constant += (self.frame.height + 8)
UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: {
sv.layoutIfNeeded()
}, completion: { b in
self.removeFromSuperview()
})
})
}
})
}
}

One of the views goes under the other view when doing transform animation with CATransform3DIdentity, even if I use autoreverses

I have reproduced the animation example so it is possible to just copy paste this to see the effect. What I would like is to do the animation on the redview, but I would want it to continue to appear above the green view after the animation, but it seems to go under it after the animation even if I set autoreverses = true. I tried putting redview.transform = .identity in the completion block but it didn't help.
import UIKit
class AnimationTest: UIViewController {
let greenView: UIView = {
let view = UIView()
view.backgroundColor = .green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(greenView)
greenView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 150).isActive = true
greenView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50).isActive = true
greenView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50).isActive = true
greenView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -150).isActive = true
view.addSubview(redView)
redView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40).isActive = true
redView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40).isActive = true
redView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.1
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.fillMode = .forwards
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.isRemovedOnCompletion = false
translationAnimation.autoreverses = true
CATransaction.setCompletionBlock {
}
redView.layer.add(translationAnimation, forKey: "translation")
CATransaction.commit()
}
}
EDIT:
Also I have another scenario where I add the view into the keywindow because I want it to appear above tab bars. But now the animation goes into the keywindow. How can I make the same animation without going into the keywindow.
import UIKit
class AnimationTest: UIViewController {
let redView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
guard let window = UIApplication.shared.keyWindow else { return }
let y = 16 + 10 + 30 + window.safeAreaInsets.top
let width = UIScreen.main.bounds.width - 8 - 8
let height = UIScreen.main.bounds.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 16 - 10 - 30 - 4 - 50
redView.frame = CGRect(x: 8, y: y, width: width, height: height)
window.addSubview(redView)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.1
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.fromValue = CATransform3DIdentity
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.autoreverses = true
CATransaction.setCompletionBlock {
}
redView.layer.add(translationAnimation, forKey: "translation") // Key doesn't matter, just call it translation.
CATransaction.commit()
}
}
You need to:
Set the fromValue to CATransform3DIdentity
Remove translationAnimation.isRemovedOnCompletion = false
I also cleaned up some of your other code. You don't need the setCompletionBlock or CATransaction at all. You also don't need fillMode.
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.5
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.fromValue = CATransform3DIdentity /// here!
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.autoreverses = true
redView.layer.add(translationAnimation, forKey: "transformKey")
}
Before
After
Edit: Red view sinks under window
You will need to adjust the layer's z position to be higher.
override func viewWillAppear(_ animated: Bool) {
guard let window = UIApplication.shared.keyWindow else { return }
let y = 16 + 10 + 30 + window.safeAreaInsets.top
let width = UIScreen.main.bounds.width - 8 - 8
let height = UIScreen.main.bounds.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 16 - 10 - 30 - 4 - 50
redView.frame = CGRect(x: 8, y: y, width: width, height: height)
window.addSubview(redView)
redView.layer.zPosition = 100 /// here!
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
0 (Default)
10
100
Half is completely obscured
Part of the view sinks
Completely fine

Animate UIView transform happens instantly instead of over duration value

I am trying to animate a view on screen in a similar way to how action sheets appear.
My router presents CustomCardViewController which has an overlay background.
After a short delay I'd like containerView too animate into view from the bottom.
Instead what is happening however is it just appears in place. There is no animation between the transition.
final class CustomCardViewController: UIViewController {
private let backgroundMask: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .init(white: 0, alpha: 0.3)
return view
}()
private lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.transform = .init(translationX: 0, y: view.frame.height)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
backgroundMask.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTapToDismiss)))
modalPresentationStyle = .overFullScreen
[backgroundMask, containerView].forEach(view.addSubview(_:))
NSLayoutConstraint.activate([
backgroundMask.topAnchor.constraint(equalTo: view.topAnchor),
backgroundMask.leadingAnchor.constraint(equalTo: view.leadingAnchor),
backgroundMask.bottomAnchor.constraint(equalTo: view.bottomAnchor),
backgroundMask.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24),
containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24),
containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200)
])
UIView.animate(withDuration: 5, delay: 0.33, options: .curveEaseOut, animations: {
self.containerView.transform = .identity
}, completion: nil)
}
}
private extension CustomCardViewController {
#objc func onTapToDismiss() {
dismiss(animated: false, completion: nil)
}
}
You need to call your animation block from another lifecycle method. You can't trigger this from viewDidLoad as the view has only loaded, there is nothing on screen yet.
Try using viewDidAppear
For animation, you need to update the constant of the constrain that you want to animate. Here, since you're trying to animate from the bottom, you need to update a vertical constrain in this case, the bottom constraint. Here's the code for animation:
final class CustomCardViewController: UIViewController {
//..
override func viewDidLoad() {
super.viewDidLoad()
NSLayoutConstraint.activate([
// remove bottom constraint from here
])
containerViewBottomConstraint = containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 250)
containerViewBottomConstraint?.isActive = true
}
var containerViewBottomConstraint: NSLayoutConstraint? // declare bottom constraint
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
containerViewBottomConstraint?.constant = -24
UIView.animate(withDuration: 5, delay: 0.33, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
}
}

UIView is not visible when inside subview

Inside my project I have Custom-DropDownView. The problem is the actual DropView is not appearing when the DropDownButton is being pressed if the DropDownButton is inside a Subview.
My structure: ViewController -> UIView -> UIStackView -> arrangedSubview ->DropdownButton
If adding the DropDownButton inside the ViewController it works just fine. However if I add it inside the StackView (where I actually need it to be) it won't show the dropView that should appear when the user taps the DropDownButton.
DropDownButton:
class DropDownBtn: UIButton, DropDownProtocol {
let label: UILabel = {
let v = UILabel()
v.font = UIFont(name: "AvenirNext-Regular", size: 15)
v.textColor = .white
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let listImage: UIImageView = {
let v = UIImageView()
v.backgroundColor = .clear
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
func dropDownPressed(string: String, image: UIImage) {
self.setTitle("", for: .normal)
self.label.text = string
self.listImage.image = image
self.dismissDropDown()
}
var dropView = DropDownView()
var height = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.layer.borderColor = UIColor.white.cgColor
self.layer.borderWidth = 1.0
self.layer.cornerRadius = 3
// add image
self.addSubview(listImage)
self.listImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
self.listImage.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.listImage.widthAnchor.constraint(equalToConstant: 22).isActive = true
self.listImage.heightAnchor.constraint(equalToConstant: 22).isActive = true
// add label
self.addSubview(label)
self.label.leadingAnchor.constraint(equalTo: self.listImage.leadingAnchor, constant: 32).isActive = true
self.label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
dropView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
dropView.delegate = self
dropView.layer.borderColor = UIColor.white.cgColor
dropView.layer.borderWidth = 1.0
dropView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(dropView)
self.superview?.bringSubviewToFront(dropView)
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
override func removeFromSuperview() {
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = false
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = false
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = false
}
var isOpen = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isOpen == false {
isOpen = true
NSLayoutConstraint.deactivate([self.height])
if self.dropView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.dropView.tableView.contentSize.height
}
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.layoutIfNeeded()
self.dropView.center.y -= self.dropView.frame.height / 2
}, completion: nil)
} else {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
}
func dismissDropDown() {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
Usage at the moment:
Inside UIView:
let dropDownButton: DropDownBtn = {
let v = DropDownBtn()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
and I add it like this (+ using autolayout, the button is displayed perfectly fine and not behind any other view):
theStackView.addArrangedSubview(self.itemView)
itemView.addSubview(dropDownButton)
I tried calling self.view.bringSubViewToFront(self.view.theUIView.dropDownButton.dropView) but that didn't do anything. actual DropDownButton seems alright. I checked it in the View-Hierarchy and I also tested that touchesBegan is actually called and it is.
So I guess the issue is how i constrain the dropView but I am completely stuck here. If anyone can help me out here I am very grateful!
If anything is unclear or you need more code, just let me know!
Update:
The reason the tableView was not visible is because I forgot to call reloadData. However that does not fix the main problem !
The tableView is not clickable when adding it inside the arrangedSubview. Only if I add it to the UIView directly it becomes clickable. In both times the view-hierarchy looks the same.. It is always at the front.
If you want to have a look yourself just let me know and I give you the link to the project.

How to mask a ViewController when presenting a view over it

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

Resources