更改zPosition不会更改视图层次结构



我正在构建一个卡视图-所选的卡在顶部,其余的在底部,堆叠在一起。他们都有相同的超视图。

所选卡片的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
}

我想你会遇到几个问题。。。

  1. 您正在设置约束显式设置帧——几乎总是在麻烦

  2. 更改layer.zPosition不会更改子视图集合中对象的顺序

  3. 使用相对于"底部"的垂直约束;顶部卡片";当试图改变卡片的位置/顺序时,可能会变得复杂

我认为更好的方法是:

  • 更新约束常量而不是帧
  • 交换子视图";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
}
}

最新更新