Ios 在自定义UIcollectionview单元格中显示计时器';s标签

Ios 在自定义UIcollectionview单元格中显示计时器';s标签,ios,swift,uicollectionview,Ios,Swift,Uicollectionview,我已经创建了一个自定义集合视图单元类,它有自己的xib文件。我想显示一个倒计时运行计时器 我有一个可以显示计时器的工作功能: class Mainview: UIviewcontroller{ // get the timer value that I want to countdown, e.g 15 mins // then I will pass the the value to the cell class } class TimerCell : UIcollec

我已经创建了一个自定义集合视图单元类,它有自己的xib文件。我想显示一个倒计时运行计时器

我有一个可以显示计时器的工作功能:

class Mainview: UIviewcontroller{
     // get the timer value that I want to countdown, e.g 15 mins
     // then I will pass the the value to the cell class
}


class TimerCell : UIcollectionviewcell{

  func timerRunning() {

            timeRemaining = timeRemaining - 1

            if (timeRemaining > 0)
            {
                let minutesLeft = Int(self.timeRemaining) / 60 % 60
                let secondsLeft = Int(self.timeRemaining) % 60
                self.timerLabel.text = "Refreshes in \(minutesLeft):\(secondsLeft)"
             }

 func runtime(){
                timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerRunning), userInfo: nil, repeats: true)
                RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
            }
}
但是因为现在我在cell类中实现了这个函数,所以计时器不会运行。我做错什么了吗?谢谢各位。

有几件事:

  • 我不建议使用对计时器处理程序的调用来

    timeRemaining = timeRemaining - 1
    
    相反,我会保存
    nextRefreshTime
    并计算当前时间和
    nextRefreshTime
    之间经过的时间,以显示剩余时间

  • 注意,当您调用
    scheduledTimer
    时,这会为您将其添加到运行循环中,因此您不必自己也将其添加到运行循环中

    或者,如果要在runloop上使用
    .commonModes
    以允许标签计时器在滚动期间继续,只需使用
    init
    创建
    计时器,然后将其添加到runloop中

    但是使用
    scheduleTimer
    将它再次添加到运行循环中是没有意义的

  • 我将不再自己计算分秒了。您可以使用
    datecomponentsformter
    很好地显示剩余时间

  • 您在
    viewDidLoad
    中谈到启动计时器。但是,
    UICollectionViewCell
    没有这样的方法(如果您创建一个名称,它将不会使用它)。相反,在
    cellForItemAt
    中调用一些方法来配置单元格

  • 模型对象(如下次数据刷新的时间)不属于单元格。单元格是暂时的UIKit对象,在滚动时会进出内存,所以它不应该跟踪这一点。为此,您应该有自己的模型,视图控制器负责在显示单元时促进该信息与单元的通信:

    • 最初使用
      nextRefreshTime
      配置单元格;及
    • 每当
      nextRefreshTime
      更改时发布一些通知
    然后,该单元将:

    • 将自身添加为该通知的观察者,更新其
      nextRefreshTime
    • 每当收到该通知时,取消任何先前的“标签更新”计时器(如有),并在必要时启动一个新的计时器
    • 标签更新处理程序应该更新标签
  • 因此:


    在你称之为的地方,最好贴满code@Sh_Khan我编辑了questionNote
    scheduledTimer
    已将其添加到当前运行循环中,因此不需要手动将其添加到运行循环中。假设
    timer
    既弱又可选,我建议在使用
    scheduledTimer
    创建和调度计时器之前,先使用
    timer?.invalidate()
    。但这一切都与你的问题无关。你到底从哪里开始计时?也就是说,您在哪里调用
    运行时
    ?你真的想在
    定时计划
    中定义
    运行时
    ?看起来好像缺少一个大括号。@Rob我在主视图出现时启动计时器。实际上,你可以忽略我的所有代码,我主要想知道在UIcollectionview单元类中显示一个正在运行的倒计时计时器?兄弟,我从API获得了持续时间,比如说15分钟。如何将其应用到您的解决方案中?在我的示例中,在
    resetTimer
    中,添加15*60而不是2*60。bro如果我想在计时器结束后调用API并重新加载collectionview,我可以在哪里调用它?我将
    刷新数据计时器的计时器处理程序放在哪里,除了重置计时器,你可能会为collection视图重新加载数据。这是一件愚蠢的事情,但如果你不叫我“兄弟”,我将不胜感激。
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var collectionView: UICollectionView!
    
        static let resetTimerNotification = Notification.Name("someuniqueidentifier")
    
        // this handles the data refreshes (or whatever), say every two minutes, or whatever
    
        private var nextRefreshTime: Date? {
            didSet {
                NotificationCenter.default.post(name: ViewController.resetTimerNotification, object: nextRefreshTime)
                refreshDataTimer?.invalidate()
                if let when = nextRefreshTime {
                    refreshDataTimer = Timer.scheduledTimer(withTimeInterval: when.timeIntervalSince(Date()), repeats: false) { [weak self] _ in
                        print("timer fired")
                        self?.resetTimer() // presumably, defer this until after the data refresh is done
                    }
                }
            }
        }
    
        private weak var refreshDataTimer: Timer?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            collectionView?.register(UINib(nibName: "TimeRemainingCell", bundle: nil), forCellWithReuseIdentifier: "TimeRemaining")
    
            resetTimer()
        }
    
        @IBAction func didTapResetButton(_ sender: Any) {
            resetTimer()
        }
    
        private func resetTimer() {
            nextRefreshTime = Date().addingTimeInterval(120) // or however you want to do this
        }
    }
    
    extension ViewController: UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return 1
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            // lets imagine that cell 0 is the TimeRemainingCell
    
            if indexPath.item == 0 {
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TimeRemaining", for: indexPath) as! TimeRemainingCell
    
                cell.configure(for: nextRefreshTime)
    
                return cell
            }
    
            ... configure other types of cells
        }
    }
    
    class TimeRemainingCell: UICollectionViewCell {
        // MARK: - Properties
    
        @IBOutlet weak var timeRemainingLabel: UILabel!
    
        private var nextRefreshTime: Date? {
            didSet {
                labelUpdateTimer?.invalidate()
    
                if nextRefreshTime != nil {
                    let timer = Timer(fire: nextRefreshTime!, interval: 0, repeats: false) { [weak self] timer in
                        // note, if cell is deallocated for any reason, let's stop the timer
    
                        guard let strongSelf = self else {
                            timer.invalidate()
                            return
                        }
    
                        strongSelf.updateLabel()
                    }
                    RunLoop.current.add(timer, forMode: .commonModes)
                    labelUpdateTimer = timer
                }
            }
        }
    
        private weak var labelUpdateTimer: Timer?
    
        /// Formatter for time remaining
        ///
        /// Note, this gets us out of manually calculating minutes and seconds remaining
    
        private static let formatter: DateComponentsFormatter = {
            let _formatter = DateComponentsFormatter()
            _formatter.unitsStyle = .positional
            _formatter.allowedUnits = [.minute, .second]
            _formatter.zeroFormattingBehavior = .pad
            return _formatter
        }()
    
    
        // MARK: - init/deinit methods
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            addNotificationObserver()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            addNotificationObserver()
        }
    
        deinit {
            labelUpdateTimer?.invalidate()
        }
    
        // MARK: - Configuration methods
    
        /// Add notification observer
    
        private func addNotificationObserver() {
            NotificationCenter.default.addObserver(forName: ViewController.resetTimerNotification, object: nil, queue: .main) { [weak self] notification in
                self?.nextRefreshTime = notification.object as? Date
                self?.updateLabel()
            }
        }
    
        /// Configure the cell for your model object.
        ///
        /// Called by collectionView(_:cellForItemAt:).
        ///
        /// Also starts the refresh timer.
        ///
        /// - Parameter object: Your model object
    
        func configure(for nextRefreshTime: Date?) {
            self.nextRefreshTime = nextRefreshTime
        }
    
        // MARK: - Label updating
    
        private func updateLabel() {
            let now = Date()
    
            if let when = nextRefreshTime {
                timeRemainingLabel.text = TimeRemainingCell.formatter.string(from: now, to: when)
            } else {
                timeRemainingLabel.text = "No time remaining"
            }
        }
    }