I implemented it using CADisplayLink to animate the text representing the percentage as shown in the image, but it does not work well with RxSwift, and I cannot discard the cell instance during the screen transition.Calling
in subscribe
let displayLink = CAD displayLink (target:self, selector:#selector(handleUpdate))
Due to the self of the part in , a circular reference occurred, and I thought that deinit could not be performed, so I looked into various things, but I couldn't solve it.
As shown in the code below, isn't the implementation that invokes animation from within the subscribe very common in Rx implementation?
Could you give me some advice to deinit the cell?
Thank you for your cooperation.
import RxSwift
import RxCocoa
class AchievementRateCell:UITableViewCell{
static let cellId="AchievementRateCellId"
let percentageNumberLabel: UILabel={
let label = UILabel()
label.text = "0%"
label.textAlignment=.center
label.font=.systemFont(ofSize:20)
label.lastLetterToSmall(value:label.text!)
return label
}()
letachievementRateCircleView=CircleProgressView()
US>deinit{
print("AchievementRateCell deinit")
}
override init(style:UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style:style, reuseIdentifier:reuseIdentifier)
print("AchievementRateCell init")
let stackView = UISTackView (arrangedSubviews: [achievementRateCircleView, percentageNumberLabel])
addSubview (stackView)
stackView.topAnchor.constraint(equalTo:topAnchor).isActive=true
stackView.bottomAnchor.constraint(equalTo:bottomAnchor).isActive=true
stackView.leadingAnchor.constraint(equalTo:leadingAnchor).isActive=true
stackView.trailingAnchor.constraint(equalTo:trailingAnchor).isActive=true
}
func bind (to viewModel: AchievementRateCellViewModel) {
viewModel.achievementRate
.subscribe(onNext:{[weak self] rate in
let basicAnimation = CABasicAnimation (keyPath: "strokeEnd")
basicAnimation.toValue=rate
basicAnimation.duration=rate*(self?.animationSpeed??1.5)
basicAnimation.fillMode=CAMediaTimingFillMode.forwards
basicAnimation.isRemovedOnCompletion=false
self?.achievementRateCircleView.foregroundLayer.add(basicAnimation, forKey: "urSoBasic")
}).disposed (by:disposeBag)
// ↓↓↓↓↓↓ Receive tableView.rx.willDisplayCell event in this part.
viewModel.achievementRate
.subscribe(onNext:{[weak self] rate in
self?.rate=rate
self?.countupOnLabel()
})
.disposed (by:disposeBag)
}
variationStartDate: Date=Date()
variable rate —Double = 0
let animationSpeed=1.5
private func countupOnLabel(){
animationStartDate=Date()
// ↓↓↓↓↓ Is there a circular reference due to the self in this part?
let displayLink = CAD displayLink (target:self, selector:#selector(handleUpdate))
displayLink.add (to:.current, forMode:.common)
}
@objc funchandleUpdate(){
let now = Date()
letelappedTime=now.timeIntervalSince(animationStartDate)
let duration = rate * animationSpeed
if elappedTime>duration{
let stringEndValue = String(format: "%.0f%", rate*100) + "%"
percentageNumberLabel.text=stringEndValue
percentageNumberLabel.lastLetterToSmall (value:stringEndValue)
} else{
let persentage=elappedTime/duration
let value = percentage * rate
let stringValue=String(format: "%.0f%", value*100) + "%"
percentageNumberLabel.text=stringValue
percentageNumberLabel.lastLetterToSmall (value:stringValue)
}
}
required init?(coder:NSCoder) {
fatalError("init(coder:)has not been implemented")
}
}
I took a hint from the answer below and solved it myself.
https://stackoverflow.com/questions/47368609/definitively-do-you-have-to-invalidate-a-cadisplaylink-when-the-controller-di
I thought there was a reason for handling "self" and "subscribe" in Rx, but simply the CADisplayLink was not used correctly.When using CADisplayLink, it seems that if you do not validate() after use, there will be strong references to self, so the cell could not be deinitiated.
I have corrected it as follows.
vardisplayLink —CADisplayLink?
@objc funchandleUpdate(){
let now = Date()
letelappedTime=now.timeIntervalSince(animationStartDate)
let duration = rate * animationSpeed
if elappedTime>duration{
let stringEndValue = String(format: "%.0f%", rate*100) + "%"
percentageNumberLabel.text=stringEndValue
percentageNumberLabel.lastLetterToSmall (value:stringEndValue)
↓↓↓ Resolved by description in this part
self.displayLink?.invalidate()
} else{
let persentage=elappedTime/duration
let value = percentage * rate
let stringValue=String(format: "%.0f%", value*100) + "%"
percentageNumberLabel.text=stringValue
percentageNumberLabel.lastLetterToSmall (value:stringValue)
}
}
© 2024 OneMinuteCode. All rights reserved.