我创建了一个带有自己的xib文件的自定义集合视图单元类。我希望显示一个倒计时运行计时器。
我有一个可以显示计时器的工作函数:
getObservable(arg){
return combineLatest(of(arg), obs$).pipe(
map(([arg, data]) => {})
);
}
但是因为现在我在单元类中实现了这个函数,所以计时器不会运行。我在这里做错了吗?谢谢你们。
答案 0 :(得分:1)
有几件事:
我不建议使用对计时器处理程序的调用
timeRemaining = timeRemaining - 1
我会保存nextRefreshTime
并计算当前时间与nextRefreshTime
之间经过的时间,以显示剩余时间。
注意,当您调用scheduledTimer
时,会将其添加到运行循环中,因此您不必自己将其添加到运行循环中。
或者,如果您想在runloop上使用.commonModes
以允许标签计时器在滚动期间继续,只需使用Timer
创建init
,然后将其添加到您的runloop
但将scheduleTimer
和add
再次用于您的运行循环是没有意义的。
我会忘记自己计算分钟数和秒数。您可以使用DateComponentsFormatter
很好地显示剩余时间。
您谈到了在viewDidLoad
启动计时器。但是UICollectionViewCell
没有这样的方法(如果你创建一个这个名字,它就不会使用它)。相反,请调用一些方法在cellForItemAt
中配置单元格。
模型对象(如下一次数据刷新时)不属于单元格。单元格是暂时的UIKit对象,当您滚动时它们会进出内存,所以它不应该跟踪它。您应该拥有自己的模型,视图控制器负责在呈现单元格时促进此信息与单元的通信:
nextRefreshTime
配置单元格;和nextRefreshTime
更改时发布一些通知。 然后细胞将:
nextRefreshTime
; 因此:
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"
}
}
}