我在UIView
中添加了不同食物的图像(我选择使用UIView
而不是UIImageView
)。图像的原始颜色为黑色,我使用.lightGray
将其更改为.alwaysTemplate
。
// the imageWithColor function on the end turns it .lightGray: [https://stackoverflow.com/a/24545102/4833705][1]
let pizzaImage = UIImage(named: "pizzaImage")?.withRenderingMode(.alwaysTemplate).imageWithColor(color1: UIColor.lightGray)
foodImages.append(pizzaImage)
我将食物图像添加到cellForRow
的UIView中
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: foodCell, for: indexPath) as! FoodCell
cell.myView.layer.contents = foodImages[indexPath.item].cgImage
return cell
}
UIView在单元格内部,并且在单元格的layoutSubviews
中添加了带有动画效果的渐变层,该动画具有微光效果,但是当单元格出现在屏幕上时,动画不会发生。
出了什么问题?
class FoodCell: UICollectionViewCell {
let myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 7
view.layer.masksToBounds = true
view.layer.contentsGravity = CALayerContentsGravity.center
view.tintColor = .lightGray
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setAnchors()
}
override func layoutSubviews() {
super.layoutSubviews()
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
gradientLayer.locations = [0, 0.5, 1]
gradientLayer.frame = myView.frame
let angle = 45 * CGFloat.pi / 180
gradientLayer.transform = CATransform3DMakeRotation(angle, 0, 0, 1)
let animation = CABasicAnimation(keyPath: "transform.translation.x")
animation.duration = 2
animation.fromValue = -self.frame.width
animation.toValue = self.frame.width
animation.repeatCount = .infinity
gradientLayer.add(animation, forKey: "...")
}
fileprivate func setAnchors() {
addSubview(myView)
myView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
myView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
myView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
myView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
}
答案 0 :(得分:0)
您可以尝试将其放在自己的函数中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React Training Project</title>
<base href="/">
</head>
<body>
<div id="app"></div>
<script src="./bundle.js"></script>
</body>
</html>
然后在您的init函数中调用它
func setUpGradient() {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
...
gradientLayer.add(animation, forKey: "...")
}
似乎您的问题可能是layoutSubviews可以被调用很多,但是只有在使用框架初始化视图时才调用init函数。另外,将设置代码置于其自身的功能中将使它更容易执行其他操作,例如在帧改变时更新渐变层的帧。
答案 1 :(得分:0)
我知道了。
我在问题下的注释中接受了@Matt的建议,并将myView添加到了单元格的contentView
属性中,而不是直接添加到该单元格中。我找不到该帖子,但我只是读到要使动画在单元格中起作用,无论动画显示在哪个视图,都需要将其添加到单元格的contentView
我从layoutSubviews中移了gradientLayer,取而代之的是使它成为惰性属性。
我也将动画移到了它自己的惰性属性中。
我使用了this answer并将gradientLayer的帧设置为单元格的bounds
属性(我最初将其设置为单元格的frame
属性)
我添加了一个函数,该函数将gradientLayer添加到myView图层的insertSublayer
属性中,并在cellForRow
中调用该函数。另外,根据我回答下的@Matt的注释,以防止渐变图层不断添加,我添加了一个检查以查看渐变是否在UIView图层的层次结构中(尽管from here是用于{不同的原因)。如果不存在,则添加,如果不添加,则不添加。
// I added both the animation and the gradientLayer here
func addAnimationAndGradientLayer() {
if let _ = (myView.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
print("it's already in here so don't readd it")
} else {
gradientLayer.add(animation, forKey: "...") // 1. added animation
myView.layer.insertSublayer(gradientLayer, at: 0) // 2. added the gradientLayer
print("it's not in here so add it")
}
}
要调用该函数以将gradientLayer添加到在cellForRow
中被调用的单元格
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: foodCell, for: indexPath) as! FoodCell
cell.removeGradientLayer() // remove the gradientLayer due to going to the background and back issues
cell.myView.layer.contents = foodImages[indexPath.item].cgImage
cell.addAnimationAndGradientLayer() // I call it here
return cell
}
单元格的更新代码
class FoodCell: UICollectionViewCell {
let myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 7
view.layer.masksToBounds = true
view.layer.contentsGravity = CALayerContentsGravity.center
view.tintColor = .lightGray
return view
}()
lazy var gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
gradientLayer.locations = [0, 0.5, 1]
gradientLayer.frame = self.bounds
let angle = 45 * CGFloat.pi / 180
gradientLayer.transform = CATransform3DMakeRotation(angle, 0, 0, 1)
return gradientLayer
}()
lazy var animation: CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "transform.translation.x")
animation.duration = 2
animation.fromValue = -self.frame.width
animation.toValue = self.frame.width
animation.repeatCount = .infinity
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
return animation
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setAnchors()
}
func addAnimationAndGradientLayer() {
// make sure the gradientLayer isn't already in myView's hierarchy before adding it
if let _ = (myView.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
print("it's already in here so don't readd it")
} else {
gradientLayer.add(animation, forKey: "...") // 1. add animation
myView.layer.insertSublayer(gradientLayer, at: 0) // 2. add gradientLayer
print("it's not in here so add it")
}
}
// this function is explained at the bottom of my answer and is necessary if you want the animation to not pause when coming from the background
func removeGradientLayer() {
myView.layer.sublayers?.removeAll()
gradientLayer.removeFromSuperlayer()
setNeedsDisplay() // these 2 might not be necessary but i called them anyway
layoutIfNeeded()
if let _ = (iconImageView.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
print("no man the gradientLayer is not removed")
} else {
print("yay the gradientLayer is removed")
}
}
fileprivate func setAnchors() {
self.contentView.addSubview(myView)
myView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
myView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
myView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
myView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
}
}
请注意,如果用户无法滚动单元格(占位符单元格),但如果由于存在问题,他们可以确保在添加前进行测试,则此BELOW效果很好
我遇到的另一个问题是当我回到背景并返回动画时不会移动。我遵循了this answer(下面的代码使用方法),尽管在同一线程中,我修改了答案,以使用this answer从一开始就开始制作动画,但但是有问题。
即使从前台回来,我仍然注意到动画有时会起作用,但滚动动画时会卡住。为了解决这个问题,我在cell.removeGradientLayer()
中调用了cellForRow
,然后再次按如下说明进行了调用。但是,滚动时它仍然卡住,但是通过调用上面的内容,它就被卡住了。它可以满足我的需要,因为我仅在实际单元加载时显示这些单元。无论如何,我都会在动画发生时禁用滚动,所以我不必担心。仅供参考,似乎只有从背景回到然后再进行滚动时,才会出现此问题。
我还不得不通过在应用程序进入后台时调用cell.removeGradientLayer()
来从单元格中删除gradientLayer,然后当它返回到前台时我必须调用cell.addAnimationAndGradientLayer()
来再次添加它。我是通过在具有collectionView的类中添加background / foreground Notifications来实现的。在随附的Notification函数中,我只需滚动显示可见的单元格,然后调用该单元格所需的功能(代码也在下面)。
class PersistAnimationView: UIView {
private var persistentAnimations: [String: CAAnimation] = [:]
private var persistentSpeed: Float = 0.0
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
func commonInit() {
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.willEnterForegroundNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func didBecomeActive() {
self.restoreAnimations(withKeys: Array(self.persistentAnimations.keys))
self.persistentAnimations.removeAll()
if self.persistentSpeed == 1.0 { //if layer was plaiyng before backgorund, resume it
self.layer.resume()
}
}
func willResignActive() {
self.persistentSpeed = self.layer.speed
self.layer.speed = 1.0 //in case layer was paused from outside, set speed to 1.0 to get all animations
self.persistAnimations(withKeys: self.layer.animationKeys())
self.layer.speed = self.persistentSpeed //restore original speed
self.layer.pause()
}
func persistAnimations(withKeys: [String]?) {
withKeys?.forEach({ (key) in
if let animation = self.layer.animation(forKey: key) {
self.persistentAnimations[key] = animation
}
})
}
func restoreAnimations(withKeys: [String]?) {
withKeys?.forEach { key in
if let persistentAnimation = self.persistentAnimations[key] {
self.layer.add(persistentAnimation, forKey: key)
}
}
}
}
extension CALayer {
func pause() {
if self.isPaused() == false {
let pausedTime: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil)
self.speed = 0.0
self.timeOffset = pausedTime
}
}
func isPaused() -> Bool {
return self.speed == 0.0
}
func resume() {
let pausedTime: CFTimeInterval = self.timeOffset
self.speed = 1.0
self.timeOffset = 0.0
self.beginTime = 0.0
// as per the amended answer comment these 2 lines out to start the animation from the beginning when coming back from the background
// let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
// self.beginTime = timeSincePause
}
}
在单元格类中,而不是制作 MyView 和UIView
的实例,而是改为使其成为PersistAnimationView
的实例:
class FoodCell: UICollectionViewCell {
let MyView: PersistAnimationView = {
let persistAnimationView = PersistAnimationView()
persistAnimationView.translatesAutoresizingMaskIntoConstraints = false
persistAnimationView.layer.cornerRadius = 7
persistAnimationView.layer.masksToBounds = true
persistAnimationView.layer.contentsGravity = CALayerContentsGravity.center
persistAnimationView.tintColor = .lightGray
return persistAnimationView
}()
// everything else in the cell class is the same
这是带有collectionView的类的通知。动画还会停止when the view disappears或reappears,因此您也必须在viewWillAppear和viewDidDisappear中进行管理。
class MyClass: UIViewController, UICollectionViewDatasource, UICollectionViewDelegateFlowLayout {
var collectionView: UICollectionView!
// MARK:- View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appHasEnteredBackground), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addAnimationAndGradientLayerInFoodCell()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
removeGradientLayerInFoodCell()
}
// MARK:- Functions for Notifications
@objc func appHasEnteredBackground() {
removeGradientLayerInFoodCell()
}
@objc func appWillEnterForeground() {
addAnimationAndGradientLayerInFoodCell()
}
// MARK:- Supporting Functions
func removeGradientLayerInFoodCell() {
// if you are using a tabBar, switch tabs, then go to the background, comeback, then switch back to this tab, without this check the animation will get stuck
if (self.view.window != nil) {
collectionView.visibleCells.forEach { (cell) in
if let cell = cell as? FoodCell {
cell.removeGradientLayer()
}
}
}
}
func addAnimationAndGradientLayerInFoodCell() {
// if you are using a tabBar, switch tabs, then go to the background, comeback, then switch back to this tab, without this check the animation will get stuck
if (self.view.window != nil) {
collectionView.visibleCells.forEach { (cell) in
if let cell = cell as? FoodCell {
cell.addAnimationAndGradientLayer()
}
}
}
}
}