我正在构建一个卡视图-所选的卡在顶部,其余的在底部,堆叠在一起。他们都有相同的超视图。
所选卡片的zPosition=0,堆叠中的卡片的zPositions:1,2,3等。预交换CardStack
当我从堆栈中选择一张卡时,我会将其与选定的卡(以及它们的zPositions(进行动画交换,就像苹果钱包一样。交换后CardStack-正确的zPositions
动画之后,zPositions会设置为正确的值,但视图层次无效。查看层次结构-Xcode可视化调试器
使用zPosition可以实现这样的动画吗?
交换动画代码:
func didSelect(cardToBeSelected: CardView) {
guard alreadySelectedCard !== cardToBeSelected else {
return
}
guard let alreadySelectedCard = alreadySelectedCard else { return }
let destinationOriginY = alreadySelectedCard.frame.origin.y
let destinationZPosition = alreadySelectedCard.layer.zPosition
alreadySelectedCard.layer.zPosition = cardToBeSelected.layer.zPosition
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.alreadySelectedCard.frame.origin.y = cardToBeSelected.frame.origin.y
cardToBeSelected.frame.origin.y = destinationOriginY
self.view.layoutSubviews()
}
animator.addCompletion { (position) in
switch position {
case .end:
cardToBeSelected.layer.zPosition = destinationZPosition
default:
break
}
}
animator.startAnimation()
self.alreadySelectedCard = cardToBeSelected
}
我想你会遇到几个问题。。。
-
您正在设置约束和显式设置帧——几乎总是在麻烦
-
更改
layer.zPosition
不会更改子视图集合中对象的顺序 -
使用相对于"底部"的垂直约束;顶部卡片";当试图改变卡片的位置/顺序时,可能会变得复杂
我认为更好的方法是:
- 更新约束常量而不是帧
- 交换子视图";z顺序";使用
insertSubview(_ view: UIView, belowSubview siblingSubview: UIView)
订购 - 将顶部约束常数值从";选择";带有";待选择";卡片
我看到你在使用SnapKit(就我个人而言,我不喜欢它,但无论如何…(
从我的快速搜索中,似乎真的很难找到SnapKit约束的引用;在飞行中";以获得其CCD_ 3值。为了避免这种情况,您可以向CardView
类添加一个属性,以保留对其"的引用;捕捉顶部约束">
这是你的代码从你的pastebin链接,修改如上所述。请考虑一下示例代码,但它可能会让您分心。大部分都是一样的——我添加了一些评论,希望能澄清我添加/更改的代码:
class ViewController: UIViewController {
private let contentInset: CGFloat = 20.0
private var scrollView: UIScrollView!
private var contentContainerView: UIView!
private var mainCardView: CardView!
private var alreadySelectedCard: CardView!
private let colors: [UIColor] = [.black, .green, .blue, .red, .yellow, .orange, .brown, .cyan, .magenta, .purple]
override func viewDidLoad() {
super.viewDidLoad()
initializeScrollView()
initializeContentContainerView()
generateCards(count: colors.count)
alreadySelectedCard = cards[0]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// first card is at the top of the view, so we'll set its offset
// inside the forEach loop to contentInset
// start top of 2nd card at bottom of first card + cardOffset
// since first card is not "at the top" yet, calculate it
var topOffset = contentInset + alreadySelectedCard.frame.height + cardOffset
// update the top offset for the rest of the cards
cards.forEach { card in
guard let thisTopConstraint = card.topConstraint else {
fatalError("Cards were not initialized correctly!!!")
}
if card == alreadySelectedCard {
thisTopConstraint.update(offset: contentInset)
} else {
thisTopConstraint.update(offset: topOffset)
topOffset += cardOffset
}
}
// animate them into view
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.contentContainerView.layoutSubviews()
}
animator.startAnimation()
}
private let cardOffset: CGFloat = 100.0
private var cards = [CardView]()
private func add(_ card: CardView) {
cards.append(card)
contentContainerView.addSubview(card)
// position all cards below the bottom of the screen
// animate them into view in viewDidAppear
let topOffset = UIScreen.main.bounds.height + 10
card.snp.makeConstraints { (make) in
let t = make.top.equalToSuperview().offset(topOffset).constraint
card.topConstraint = t
make.left.equalToSuperview().offset(contentInset)
make.right.equalToSuperview().offset(-contentInset)
make.height.equalTo(card.snp.width).multipliedBy(0.5)
make.bottom.lessThanOrEqualToSuperview()
}
}
private func generateCards(count: Int) {
for index in 0..<count {
let card = CardView(delegate: self)
card.backgroundColor = colors[index % colors.count]
card.layer.cornerRadius = 10
add(card)
}
}
}
extension ViewController: CardViewDelegate {
func didSelect(cardToBeSelected: CardView) {
guard alreadySelectedCard !== cardToBeSelected else {
return
}
guard
// get the top "snap constraint" from alreadySelectedCard
let alreadySnapConstraint = alreadySelectedCard.topConstraint,
// get its constraint reference so we can get its .constant
let alreadyConstraint = alreadySnapConstraint.layoutConstraints.first,
// get the top "snap constraint" from cardToBeSelected
let toBeSnapConstraint = cardToBeSelected.topConstraint,
// get its constraint reference so we can get its .constant
let toBeConstraint = toBeSnapConstraint.layoutConstraints.first
else { return }
// save the constant (the Top Offset) from cardToBeSelected
let tmpOffset = toBeConstraint.constant
// update the Top Offset for cardToBeSelected with the
// constant from alreadySelectedCard (it will be contentInset unless something has changed)
toBeSnapConstraint.update(offset: alreadyConstraint.constant)
// update the Top Offset for alreadySelectedCard
alreadySnapConstraint.update(offset: tmpOffset)
// swap the "z-order" of the views, instead of the view layers
contentContainerView.insertSubview(alreadySelectedCard, belowSubview: cardToBeSelected)
// animate the change
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.contentContainerView.layoutSubviews()
}
animator.startAnimation()
// update alreadySelectedCard
self.alreadySelectedCard = cardToBeSelected
}
}
extension ViewController {
private func initializeScrollView() {
scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.backgroundColor = .lightGray
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
}
private func initializeContentContainerView() {
contentContainerView = UIView()
scrollView.addSubview(contentContainerView)
contentContainerView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
make.width.equalToSuperview()
}
}
}
protocol CardViewDelegate {
func didSelect(cardToBeSelected: CardView)
}
class CardView: UIView {
var tapGestureRecognizer: UITapGestureRecognizer!
var delegate: CardViewDelegate?
// snap constraint reference so we can modify it later
weak var topConstraint: Constraint?
convenience init(delegate: CardViewDelegate) {
self.init(frame: .zero)
self.delegate = delegate
}
override init(frame: CGRect) {
super.init(frame: frame)
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapCard))
tapGestureRecognizer.delegate = self
addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func didTapCard() {
delegate?.didSelect(cardToBeSelected: self)
}
}
extension CardView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}