我正在使用 UIView.transition 来翻转卡片。
在对其不起作用进行故障排除时,我偶然发现了一种使其起作用的方法 - 但我不知道为什么。我希望有人可以查看下面的两个代码块,并帮助我理解为什么一个有效而另一个无效。我觉得很奇怪。
首先,这是实际工作的代码块。卡片翻转在视觉上完美无瑕。
UIView.animate(withDuration: 0.01) { imageView.alpha = 1.0 imageView.layoutIfNeeded() // Works with and without this layoutIfNeeded() } completion: { (true) in UIView.transition(with: imageView, duration: 1.2, options: animation) { imageView.image = endingImage imageView.layoutIfNeeded() // Works with and without this layoutIfNeeded() } completion: { (true) in if self.dealTicketState.isTicketFaceUp == true { self.faceDownView.alpha = 0.0 } else { self.faceDownView.alpha = 1.0 } UIView.animate(withDuration: 0.01) { self.coveringLabel.backgroundColor = .clear self.coveringLabel.layoutIfNeeded() imageView.removeFromSuperview() self.tickNumLabel.alpha = originalTicketNumAlpha } } }
但我不明白为什么我似乎需要将 UIView.transition() 包装到调用 UIView.animate() 的完成处理程序中才能使翻转动画起作用。
*(注意:如果我将“imageView.alpha = 1.0”从 animate() 块中拉出并在调用 UIView.animate() 之前立即将其放置 - 翻转动画不会发生(无论有或没有layoutIfNeeded())调用)。它只是切换图像。*
现在,这是我
期望工作的代码 - 但是当我使用此代码而不是上面的代码时,没有“翻转”转换。卡片图像立即在正面朝上和正面朝下图像之间切换。这里的“UIView.transition”调用与上面代码中的调用相同。这里唯一的区别是它不是包装在 0.01 秒的 UIView.animate 完成块中。
imageView.alpha = 1.0
imageView.layoutIfNeeded()
UIView.transition(with: imageView, duration: 1.2, options: animation) {
imageView.image = endingImage
imageView.layoutIfNeeded() // same behaviour with and without this line
} completion: { (true) in
if self.dealTicketState.isTicketFaceUp == true { self.faceDownView.alpha = 0.0 } else { self.faceDownView.alpha = 1.0 }
UIView.animate(withDuration: 0.01) {
self.coveringLabel.backgroundColor = .clear
self.coveringLabel.layoutIfNeeded()
imageView.removeFromSuperview()
self.tickNumLabel.alpha = originalTicketNumAlpha
}
}
此转换结束了我的 FlipTicket() 函数。在这两种情况下,此转换之前的代码是相同的。我不打算包含它,因为我认为没有必要理解这个问题 - 但话又说回来 - 我知道什么?以下是上述剪辑之前的内容:
func flipTicket() {
let originalTicketNumAlpha = self.tickNumLabel.alpha
self.tickNumLabel.alpha = 0.0
let tempFaceDownImage:UIImage = self.dealTicketState.faceDownImage
let tempFaceUpImage:UIImage = getCurrentFaceUpImage()
var endingImage:UIImage = self.dealTicketState.faceDownImage
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
imageView.clipsToBounds = true
imageView.alpha = 0.0
self.coveringLabel.alpha = 1.0
self.coveringLabel.backgroundColor = .black
self.coveringLabel.layoutIfNeeded()
var animation:UIView.AnimationOptions = .transitionFlipFromLeft
if faceDownView.alpha == 1.0 {
animation = .transitionFlipFromRight
imageView.image = tempFaceDownImage
endingImage = tempFaceUpImage
} else {
animation = .transitionFlipFromLeft
imageView.image = tempFaceUpImage
}
self.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor),
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
imageView.layoutIfNeeded()
背景: 此代码是代表一张扑克牌的自定义 UI 控件的一部分。它由几个子视图组成。最上面的子视图最初是一个 UIImageView,它保存卡片背面的图像。这让我只需切换顶视图的 Alpha 即可将卡片显示为面朝上或面朝下。然后我向控件添加了一个 topMost 视图 - 一个带有“background = .clear”的 UILabel 并附加了一个 UITapGestureRecognizer。当点击控件时,将调用此函数,该函数旨在使卡片翻转动画。
为了构造动画,我调用 getCurrentFaceUp() 函数,该函数临时将卡片的 faceDownView 的 alpha 设置为 0(这样我就可以拍摄卡片下方当前配置的快照)。它返回卡片“正面朝上”视图的 UIImage。我已经有一个 FaceDown 视图的 UIImage。这些是我需要进行转换的 2 张图像。
所以...然后我将 topMost UILabel 的背景颜色设置为 .black,创建一个新的临时 UIImageView 并将其放置在现有控件的顶部。我将临时 imageView 设置为最初显示控件上当前可见的 2 个图像中的任意一个。然后我运行翻转过渡,更改背景控件的配置以匹配新状态,将标签背景更改回 .clear 并处理临时 UIImageView。
(如果有更好的方法来实现这一点,我愿意听到它,但这篇文章的主要目的是理解为什么我的代码看起来表现得很奇怪。)
当我寻找一种为卡片翻转设置动画的方法时,我发现了一个 YouTube 视频,该视频演示了 UIView.transition() 和翻转动画。它不需要使用 UIView.animate() 包装器来使其工作 - 所以我很确定是我做错了什么 - 但我花了几个小时测试变体并寻找其他有这个问题的人我一直没能找到答案。
提前非常感谢任何可以帮助我了解这里发生的事情的人......
看看这个...
我们将从两个“卡片视图”子类开始 - 正面和背面(我们将在下一步中“设计”它们):
class CardFrontView: UIView {
}
class CardBackView: UIView {
}
然后是“扑克牌视图”类,其中包含“前”视图(青色)和“后”视图(红色)。在初始化时,我们添加子视图并将“前”视图设置为隐藏。点击后,我们将在前视图和后视图之间运行翻转过渡:
class PlayingCardView: UIView {
let cardFront: CardFrontView = CardFrontView()
let cardBack: CardBackView = CardBackView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// add both card views
// constraining all 4 sides to self
[cardFront, cardBack].forEach { v in
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: topAnchor),
v.leadingAnchor.constraint(equalTo: leadingAnchor),
v.trailingAnchor.constraint(equalTo: trailingAnchor),
v.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
cardFront.backgroundColor = .cyan
cardBack.backgroundColor = .red
// start with cardFront hidden
cardFront.isHidden = true
// add a tap recognizer
let t = UITapGestureRecognizer(target: self, action: #selector(flipMe))
addGestureRecognizer(t)
}
@objc func flipMe() -> Void {
// fromView is the one that is NOT hidden
let fromView = cardBack.isHidden ? cardFront : cardBack
// toView is the one that IS hidden
let toView = cardBack.isHidden ? cardBack : cardFront
// if we're going from back-to-front
// flip from left
// else
// flip from right
let direction: UIView.AnimationOptions = cardBack.isHidden ? .transitionFlipFromRight : .transitionFlipFromLeft
UIView.transition(from: fromView,
to: toView,
duration: 0.5,
options: [direction, .showHideTransitionViews],
completion: { b in
// if we want to do something on completion
})
}
}
然后这是一个简单的控制器示例:
class FlipCardVC: UIViewController {
let pCard: PlayingCardView = PlayingCardView()
override func viewDidLoad() {
super.viewDidLoad()
let g = view.safeAreaLayoutGuide
pCard.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pCard)
NSLayoutConstraint.activate([
pCard.centerXAnchor.constraint(equalTo: g.centerXAnchor),
pCard.centerYAnchor.constraint(equalTo: g.centerYAnchor),
pCard.widthAnchor.constraint(equalToConstant: 200.0),
pCard.heightAnchor.constraint(equalTo: pCard.widthAnchor, multiplier: 1.5),
])
}
}
结果:
PlayingCardView
功能没有任何更改...只需几行新行来设置样式...
卡片正面视图 - 带圆角、边框,角部和中心有标签:class CardFrontView: UIView {
var theLabels: [UILabel] = []
var cardID: Int = 0 {
didSet {
theLabels.forEach {
$0.text = "\(cardID)"
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
for i in 1...5 {
let v = UILabel()
v.font = .systemFont(ofSize: 24.0)
v.translatesAutoresizingMaskIntoConstraints = false
addSubview(v)
switch i {
case 1:
v.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true
v.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true
case 2:
v.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true
v.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0).isActive = true
case 3:
v.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0).isActive = true
v.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true
case 4:
v.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0).isActive = true
v.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0).isActive = true
default:
v.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
v.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
theLabels.append(v)
}
layer.cornerRadius = 6
// border
layer.borderWidth = 1.0
layer.borderColor = UIColor.gray.cgColor
}
}
看起来像这样:
class CardBackView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
layer.cornerRadius = 6
// border
layer.borderWidth = 1.0
layer.borderColor = UIColor.gray.cgColor
layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
// simple cross-hatch pattern
let hReplicatorLayer = CAReplicatorLayer()
let vReplicatorLayer = CAReplicatorLayer()
let line = CAShapeLayer()
let pth = UIBezierPath()
pth.move(to: CGPoint(x: 0.0, y: 0.0))
pth.addLine(to: CGPoint(x: 20.0, y: 20.0))
pth.move(to: CGPoint(x: 20.0, y: 0.0))
pth.addLine(to: CGPoint(x: 0.0, y: 20.0))
line.strokeColor = UIColor.yellow.cgColor
line.lineWidth = 1
line.path = pth.cgPath
var instanceCount = Int((bounds.maxX + 0.0) / 20.0)
hReplicatorLayer.instanceCount = instanceCount
hReplicatorLayer.instanceTransform = CATransform3DMakeTranslation(20, 0, 0)
instanceCount = Int((bounds.maxY + 0.0) / 20.0)
vReplicatorLayer.instanceCount = instanceCount
vReplicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, 20, 0)
hReplicatorLayer.addSublayer(line)
vReplicatorLayer.addSublayer(hReplicatorLayer)
layer.addSublayer(vReplicatorLayer)
}
}
看起来像这样:
class PlayingCardView: UIView {
var cardID: Int = 0 { didSet { cardFront.cardID = cardID } }
let cardFront: CardFrontView = CardFrontView()
let cardBack: CardBackView = CardBackView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// add both card views
// constraining all 4 sides to self
[cardFront, cardBack].forEach { v in
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: topAnchor),
v.leadingAnchor.constraint(equalTo: leadingAnchor),
v.trailingAnchor.constraint(equalTo: trailingAnchor),
v.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
cardFront.backgroundColor = .white
cardBack.backgroundColor = .red
// start with cardFront hidden
cardFront.isHidden = true
// add a tap recognizer
let t = UITapGestureRecognizer(target: self, action: #selector(flipMe))
addGestureRecognizer(t)
}
@objc func flipMe() -> Void {
// fromView is the one that is NOT hidden
let fromView = cardBack.isHidden ? cardFront : cardBack
// toView is the one that IS hidden
let toView = cardBack.isHidden ? cardBack : cardFront
// if we're going from back-to-front
// flip from left
// else
// flip from right
let direction: UIView.AnimationOptions = cardBack.isHidden ? .transitionFlipFromRight : .transitionFlipFromLeft
UIView.transition(from: fromView,
to: toView,
duration: 0.5,
options: [direction, .showHideTransitionViews],
completion: { b in
// if we want to do something on completion
})
}
}
最后,相同的控制器示例:
class FlipCardVC: UIViewController {
let pCard: PlayingCardView = PlayingCardView()
override func viewDidLoad() {
super.viewDidLoad()
let g = view.safeAreaLayoutGuide
pCard.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pCard)
NSLayoutConstraint.activate([
pCard.centerXAnchor.constraint(equalTo: g.centerXAnchor),
pCard.centerYAnchor.constraint(equalTo: g.centerYAnchor),
pCard.widthAnchor.constraint(equalToConstant: 200.0),
pCard.heightAnchor.constraint(equalTo: pCard.widthAnchor, multiplier: 1.5),
])
pCard.cardID = 5
}
}
这是新结果: