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

时间:2018-05-20 16:00:10

标签: ios swift uicollectionview

我创建了一个带有自己的xib文件的自定义集合视图单元类。我希望显示一个倒计时运行计时器。

我有一个可以显示计时器的工作函数:

getObservable(arg){
   return combineLatest(of(arg), obs$).pipe(
     map(([arg, data]) => {})
   );
}

但是因为现在我在单元类中实现了这个函数,所以计时器不会运行。我在这里做错了吗?谢谢你们。

1 个答案:

答案 0 :(得分:1)

有几件事:

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

    timeRemaining = timeRemaining - 1
    

    我会保存nextRefreshTime并计算当前时间与nextRefreshTime之间经过的时间,以显示剩余时间。

  2. 注意,当您调用scheduledTimer时,会将其添加到运行循环中,因此您不必自己将其添加到运行循环中。

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

    但将scheduleTimeradd再次用于您的运行循环是没有意义的。

  3. 我会忘记自己计算分钟数和秒数。您可以使用DateComponentsFormatter很好地显示剩余时间。

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

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

    • 最初使用nextRefreshTime配置单元格;和
    • nextRefreshTime更改时发布一些通知。

    然后细胞将:

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

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