动画约束改变奇怪的结果
Animating constraints changes weird result
我有这个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()
}
}
}
这个 class 基本上创建了一个视图,当按下它时另一个视图在它下面打开
这是我调用 class 的代码:(在视图中加载):
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
但是动画结果不是我所期望的:
如您所见,动画是跳跃的,因为它是从中间而不是顶部开始的。
我希望蓝色视图在粉红色视图打开和关闭时保持原位。
提前致谢
这似乎是一种创建自定义视图的相当复杂的方法,但是...
首先,在视图控制器的视图上执行 NOT 设置 .translatesAutoresizingMaskIntoConstraints = false
。所以,在你的 viewDidLoad()
func:
let cell = Cell(tint: .blue, openedView: myView)
// DO NOT DO THIS
//view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cell.view)
奇怪的动画是因为您为错误的视图设置了动画。
将 changeOpened(...)
函数的末尾更改为:
guard let sv = self.view.superview else {
return
}
UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
//self.view.layoutIfNeeded()
sv.layoutIfNeeded()
}
我有这个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()
}
}
}
这个 class 基本上创建了一个视图,当按下它时另一个视图在它下面打开
这是我调用 class 的代码:(在视图中加载):
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
但是动画结果不是我所期望的:
如您所见,动画是跳跃的,因为它是从中间而不是顶部开始的。 我希望蓝色视图在粉红色视图打开和关闭时保持原位。
提前致谢
这似乎是一种创建自定义视图的相当复杂的方法,但是...
首先,在视图控制器的视图上执行 NOT 设置 .translatesAutoresizingMaskIntoConstraints = false
。所以,在你的 viewDidLoad()
func:
let cell = Cell(tint: .blue, openedView: myView)
// DO NOT DO THIS
//view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cell.view)
奇怪的动画是因为您为错误的视图设置了动画。
将 changeOpened(...)
函数的末尾更改为:
guard let sv = self.view.superview else {
return
}
UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
//self.view.layoutIfNeeded()
sv.layoutIfNeeded()
}