I have this class:
class Cell {
/// The view that contains everything.
///
/// Use this view to present this `Cell`.
var view: UIView
/// The view that contains the content of the cell without `openedView`.
var cellView: UIView
var openedView: UIView
var topOpenedView: UIView
var button: UIButton! = nil
private var isOpenedProtected: Bool = false
/// The visibility of ```openedView```.
///
/// Changing this variable is the same as ```changeOpened(to:animated:)``` with animation.
var isOpened: Bool {
get {
return isOpenedProtected
}
set {
changeOpened(to: newValue, animated: true)
}
}
private var openedViewHeightConstraint: NSLayoutConstraint! = nil
private var openedViewBottomConstraint: NSLayoutConstraint! = nil
lazy private var buttonHandler: UIActionHandler = { _ in
self.isOpened.toggle()
}
init(tint color: UIColor, openedView givenOpenedView: UIView) {
self.openedView = givenOpenedView
openedView.translatesAutoresizingMaskIntoConstraints = false
// VIEW
self.view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
self.topOpenedView = UIView(frame: .zero)
topOpenedView.clipsToBounds = true
topOpenedView.translatesAutoresizingMaskIntoConstraints = false
topOpenedView.addSubview(openedView)
openedView.clipsToBounds = true
topOpenedView.topAnchor.constraint(equalTo: openedView.topAnchor).isActive = true
topOpenedView.leadingAnchor.constraint(equalTo: openedView.leadingAnchor).isActive = true
topOpenedView.trailingAnchor.constraint(equalTo: openedView.trailingAnchor).isActive = true
self.openedViewBottomConstraint = topOpenedView.bottomAnchor.constraint(equalTo: openedView.bottomAnchor)
openedViewBottomConstraint.isActive = false
self.openedViewHeightConstraint = topOpenedView.heightAnchor.constraint(equalToConstant: 0)
openedViewHeightConstraint.isActive = true
for constraint in openedView.constraints {
constraint.priority -= 1
}
// ---
self.cellView = UIView(frame: .zero)
cellView.translatesAutoresizingMaskIntoConstraints = false
cellView.clipsToBounds = true
// Button
self.button = UIButton(frame: .zero, primaryAction: UIAction(handler: buttonHandler))
button.translatesAutoresizingMaskIntoConstraints = false
// Add subviews
cellView.addSubview(button)
cellView.heightAnchor.constraint(equalToConstant: 45).isActive = true
cellView.leadingAnchor.constraint(equalTo: button.leadingAnchor).isActive = true
cellView.trailingAnchor.constraint(equalTo: button.trailingAnchor).isActive = true
cellView.topAnchor.constraint(equalTo: button.topAnchor).isActive = true
cellView.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true
cellView.backgroundColor = color
cellView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
// View
view.layer.cornerRadius = 10
view.backgroundColor = .clear
view.addSubview(cellView)
view.addSubview(topOpenedView)
view.topAnchor.constraint(equalTo: cellView.topAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: cellView.leadingAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: topOpenedView.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: cellView.trailingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: topOpenedView.trailingAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: topOpenedView.bottomAnchor).isActive = true
topOpenedView.topAnchor.constraint(equalTo: cellView.bottomAnchor).isActive = true
cellView.bringSubviewToFront(button)
}
func changeOpened(to open: Bool, animated: Bool) {
if isOpenedProtected == open { return }
isOpenedProtected = open
let duration: Double = animated ? 0.5 : 0.0
self.view.layoutIfNeeded()
print(open ? "Opening" : "Closing")
self.openedViewHeightConstraint.isActive = !open
self.openedViewBottomConstraint.isActive = open
UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
self.view.layoutIfNeeded()
}
}
}
This class basically creates a View that when pressing it another View opens below it
This is my code calling the class: (in view did load):
let myView = UIView(frame: .zero)
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .systemPink
myView.heightAnchor.constraint(equalToConstant: 120).isActive = true
let cell = Cell(tint: .blue, openedView: myView)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cell.view)
cell.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
cell.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
cell.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
But the animation result is not what I expected:
as you can see, the animation is jumpy because its starting from the middle instead of the top. I want the blue view to stay in its place when the pink view opens and closes.
Thanks in advance
That seems like a rather convoluted way to create a custom view, but...
First, do NOT set .translatesAutoresizingMaskIntoConstraints = false
on a view controller's view. So, in your viewDidLoad()
func:
let cell = Cell(tint: .blue, openedView: myView)
// DO NOT DO THIS
//view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cell.view)
The weird animation is because you're animating the wrong view.
Change the end of your changeOpened(...)
func to this:
guard let sv = self.view.superview else {
return
}
UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
//self.view.layoutIfNeeded()
sv.layoutIfNeeded()
}