I want to receive the TableViewCell layout event in RxSwift's "tableView.rx.willDisplayCell" and animate the text using CAD DisplayLink in subscribe.

Asked 2 years ago, Updated 2 years ago, 83 views

partial animation

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")
    }
}



swift uitableview rx-swift cyclic-reference

2022-09-30 20:24

1 Answers

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)
        }

    }


2022-09-30 20:24

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.