如何禁用 UIStackView 默认 show/hide 动画?
How do I disable UIStackView default show/hide animation?
这可能是个奇怪的问题。当我们在其中显示或隐藏视图时,我们在 UIStackView 中免费获得动画。但是这种行为与我拥有的另一个动画相冲突。那么有没有办法禁用 UIStackView 的默认动画?
我希望它只显示或隐藏没有任何动画的子视图。如何使用 swift 实现此目的?
已更新
因此,如果我执行 view2.isHidden = true
,StackView 将默认隐藏带有折叠动画的 View2。我希望它在没有动画的情况下强制隐藏
在没有任何其他信息的情况下,我猜测您正在按照以下思路做某事:
self.view2.isHidden.toggle()
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
您获得堆栈视图动画是因为:
// nothing happening between
// hide / show arranged subview
// and
// the animation block
// so, this is the START of the "animation"
self.view2.isHidden.toggle()
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
避免这种情况的各种方法,包括:
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
// hide / show arranged subview AFTER animation block
self.view2.isHidden.toggle()
和:
// hide / show arranged subview
self.view2.isHidden.toggle()
// force layout update
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// now start the animation
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
这里有一个完整的例子来展示差异:
class ViewController: UIViewController {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
return v
}()
let animView = UILabel()
let view1 = UILabel()
let view2 = UILabel()
let stackContainer = UIView()
var animLeadingConstraint: NSLayoutConstraint!
var animTrailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// add three buttons at the top
let btnsStack = UIStackView()
btnsStack.spacing = 20
btnsStack.distribution = .fillEqually
["Default", "Fix 1", "Fix 2"].forEach { str in
let b = UIButton()
b.setTitle(str, for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b.backgroundColor = .systemGreen
b.addTarget(self, action: #selector(btnTap(_:)), for: .touchUpInside)
btnsStack.addArrangedSubview(b)
}
for (v, s) in zip([animView, view1, view2], ["Will Animate", "View 1", "View 2"]) {
v.text = s
v.textAlignment = .center
v.layer.borderWidth = 2
v.layer.borderColor = UIColor.red.cgColor
}
animView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
stackContainer.backgroundColor = .systemTeal
[btnsStack, stackView, stackContainer, animView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
stackView.addArrangedSubview(view1)
stackView.addArrangedSubview(view2)
stackContainer.addSubview(stackView)
view.addSubview(btnsStack)
view.addSubview(stackContainer)
view.addSubview(animView)
let g = view.safeAreaLayoutGuide
// Leading and Trailing constraints for the animView
// so we can "slide" it back and forth
animLeadingConstraint = animView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0)
animLeadingConstraint.priority = .defaultHigh
animTrailingConstraint = animView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0)
animTrailingConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
btnsStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
stackContainer.topAnchor.constraint(equalTo: btnsStack.bottomAnchor, constant: 40.0),
stackContainer.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stackView.topAnchor.constraint(equalTo: stackContainer.topAnchor),
stackView.leadingAnchor.constraint(equalTo: stackContainer.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: stackContainer.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: stackContainer.bottomAnchor),
view1.widthAnchor.constraint(equalToConstant: 240.0),
view1.heightAnchor.constraint(equalToConstant: 160.0),
view2.widthAnchor.constraint(equalTo: view1.widthAnchor),
view2.heightAnchor.constraint(equalTo: view1.heightAnchor),
animView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
animView.widthAnchor.constraint(equalToConstant: 160.0),
animView.heightAnchor.constraint(equalToConstant: 40.0),
animLeadingConstraint,
])
}
@objc func btnTap(_ sender: Any?) -> Void {
guard let btn = sender as? UIButton else {
return
}
if btn.currentTitle == "Fix 1" {
fixedApproachOne()
} else if btn.currentTitle == "Fix 2" {
fixedApproachTwo()
} else {
defaultApproach()
}
}
func defaultApproach() -> Void {
// nothing happening between
// hide / show arranged subview
// and
// the animation block
// so, this is the START of the "animation"
self.view2.isHidden.toggle()
runAnim()
}
func fixedApproachOne() -> Void {
// start the animation
runAnim()
// hide / show arranged subview AFTER animation block
self.view2.isHidden.toggle()
}
func fixedApproachTwo() -> Void {
// hide / show arranged subview
self.view2.isHidden.toggle()
// force layout update
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// now start the animation
runAnim()
}
func runAnim() -> Void {
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
看起来像这样:
这可能是个奇怪的问题。当我们在其中显示或隐藏视图时,我们在 UIStackView 中免费获得动画。但是这种行为与我拥有的另一个动画相冲突。那么有没有办法禁用 UIStackView 的默认动画?
我希望它只显示或隐藏没有任何动画的子视图。如何使用 swift 实现此目的?
已更新
因此,如果我执行 view2.isHidden = true
,StackView 将默认隐藏带有折叠动画的 View2。我希望它在没有动画的情况下强制隐藏
在没有任何其他信息的情况下,我猜测您正在按照以下思路做某事:
self.view2.isHidden.toggle()
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
您获得堆栈视图动画是因为:
// nothing happening between
// hide / show arranged subview
// and
// the animation block
// so, this is the START of the "animation"
self.view2.isHidden.toggle()
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
避免这种情况的各种方法,包括:
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
// hide / show arranged subview AFTER animation block
self.view2.isHidden.toggle()
和:
// hide / show arranged subview
self.view2.isHidden.toggle()
// force layout update
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// now start the animation
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
这里有一个完整的例子来展示差异:
class ViewController: UIViewController {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
return v
}()
let animView = UILabel()
let view1 = UILabel()
let view2 = UILabel()
let stackContainer = UIView()
var animLeadingConstraint: NSLayoutConstraint!
var animTrailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// add three buttons at the top
let btnsStack = UIStackView()
btnsStack.spacing = 20
btnsStack.distribution = .fillEqually
["Default", "Fix 1", "Fix 2"].forEach { str in
let b = UIButton()
b.setTitle(str, for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b.backgroundColor = .systemGreen
b.addTarget(self, action: #selector(btnTap(_:)), for: .touchUpInside)
btnsStack.addArrangedSubview(b)
}
for (v, s) in zip([animView, view1, view2], ["Will Animate", "View 1", "View 2"]) {
v.text = s
v.textAlignment = .center
v.layer.borderWidth = 2
v.layer.borderColor = UIColor.red.cgColor
}
animView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
stackContainer.backgroundColor = .systemTeal
[btnsStack, stackView, stackContainer, animView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
stackView.addArrangedSubview(view1)
stackView.addArrangedSubview(view2)
stackContainer.addSubview(stackView)
view.addSubview(btnsStack)
view.addSubview(stackContainer)
view.addSubview(animView)
let g = view.safeAreaLayoutGuide
// Leading and Trailing constraints for the animView
// so we can "slide" it back and forth
animLeadingConstraint = animView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0)
animLeadingConstraint.priority = .defaultHigh
animTrailingConstraint = animView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0)
animTrailingConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
btnsStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
stackContainer.topAnchor.constraint(equalTo: btnsStack.bottomAnchor, constant: 40.0),
stackContainer.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stackView.topAnchor.constraint(equalTo: stackContainer.topAnchor),
stackView.leadingAnchor.constraint(equalTo: stackContainer.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: stackContainer.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: stackContainer.bottomAnchor),
view1.widthAnchor.constraint(equalToConstant: 240.0),
view1.heightAnchor.constraint(equalToConstant: 160.0),
view2.widthAnchor.constraint(equalTo: view1.widthAnchor),
view2.heightAnchor.constraint(equalTo: view1.heightAnchor),
animView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
animView.widthAnchor.constraint(equalToConstant: 160.0),
animView.heightAnchor.constraint(equalToConstant: 40.0),
animLeadingConstraint,
])
}
@objc func btnTap(_ sender: Any?) -> Void {
guard let btn = sender as? UIButton else {
return
}
if btn.currentTitle == "Fix 1" {
fixedApproachOne()
} else if btn.currentTitle == "Fix 2" {
fixedApproachTwo()
} else {
defaultApproach()
}
}
func defaultApproach() -> Void {
// nothing happening between
// hide / show arranged subview
// and
// the animation block
// so, this is the START of the "animation"
self.view2.isHidden.toggle()
runAnim()
}
func fixedApproachOne() -> Void {
// start the animation
runAnim()
// hide / show arranged subview AFTER animation block
self.view2.isHidden.toggle()
}
func fixedApproachTwo() -> Void {
// hide / show arranged subview
self.view2.isHidden.toggle()
// force layout update
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// now start the animation
runAnim()
}
func runAnim() -> Void {
// animate constraint change
self.animLeadingConstraint.isActive.toggle()
self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
看起来像这样: