Я получил это работает.
Я воспользовался советом Мэтта в комментариях к этому вопросу и добавил myView к свойству contentView
ячейки, а не непосредственно к ячейке. Я не могу найти сообщение, но я только что прочитал, чтобы анимации работали в ячейке, какой бы ни был просмотр, необходимо добавить анимацию в ячейку contentView
Я переместил градиентный слой из layoutSubviews и вместо этого сделал его ленивым свойством.
Я также переместил анимацию в собственное свойство lazy.
Я использовал этот ответ и установил для градиентного слоя свойство bounds
ячейки (изначально у меня было свойство frame
ячейки)
Я добавил функцию, которая добавляет градиентный слой в свойство insertSublayer
слоя myView, и вызвал эту функцию в cellForRow
. Также согласно комментариям @ Matt под моим ответом, чтобы не допустить постоянного добавления градиента, я добавляю проверку, чтобы увидеть, находится ли градиент в иерархии слоя UIView (отсюда идея , хотя используется по другой причине). Если его нет, я добавляю, а если нет, то добавляю.
// 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")
}
}
Чтобы вызвать функцию добавления градиента в ячейку, в которую она вызывается 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
}
}
В качестве дополнительного примечания это НИЖЕ прекрасно работает, если пользователи НЕ МОГУТ прокручивать ячейки (ячейки-заполнители), но если они МОГУТ обязательно проверять перед добавлением, потому что это глючит
Другая проблема, с которой я столкнулся, заключалась в том, что когда я уходил на задний план и возвращался, анимация не двигалась. Я следовал этому ответу (код ниже о том, как его использовать), который работает, хотя в той же теме я изменил этот ответ, чтобы использовать этот ответ , чтобы запустить анимацию с самого начала, которая работает НО есть проблемы.
Я заметил, хотя я вернулся с переднего плана, и анимация иногда работала, когда я прокручивал анимацию, которая застряла. Чтобы обойти это, я позвонил cell.removeGradientLayer()
в cellForRow
, а затем снова, как объяснено ниже. Тем не менее, он все еще застрял при прокрутке, но, позвонив выше, он отклеился. Он работает для того, для чего он мне нужен, потому что я показываю эти ячейки только во время загрузки самих ячеек. Я отключаю прокрутку, когда анимация происходит в любом случае, поэтому мне не нужно беспокоиться об этом. К вашему сведению, эта застрявшая проблема возникает только при возврате из фона и затем прокрутки .
Мне также пришлось удалить градиентный слой из ячейки, вызвав cell.removeGradientLayer()
, когда приложение перешло в фоновый режим, а затем, когда оно вернулось на передний план, мне пришлось вызвать cell.addAnimationAndGradientLayer()
, чтобы добавить его снова. Я сделал это, добавив фоновые / передние уведомления в класс, который имеет collectionView. В сопровождающих функциях уведомлений я просто прокручиваю видимые ячейки и вызываю необходимые функции ячейки (код также приведен ниже).
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. Анимации также останавливаются , когда представление исчезает или вновь появляется , поэтому вам придется управлять этим в 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()
}
}
}
}
}