如何在一个 viewController 中管理多个计时器?
Posted
技术标签:
【中文标题】如何在一个 viewController 中管理多个计时器?【英文标题】:How to manage multiple timers in one viewController? 【发布时间】:2020-05-12 11:00:01 【问题描述】:我正在开发计时器应用程序,但无法理解如何为每个单元格设置多个计时器。
我在 didSelectRowAt 启动和暂停计时器:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! TimerTableViewCell
let item = timers.items[indexPath.row]
item.toggle()
print(item)
startPauseTimer(for: cell, with: item)
这是我的 startPauseTimer 代码:
var timer = Timer()
func startPauseTimer(for cell: TimerTableViewCell, with item: Timers)
if !item.isStarted
timer.invalidate()
else
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) timer in
item.seconds -= 1
cell.timerTime.text = self.formattedTime(time: TimeInterval(item.seconds))
if item.seconds < 1
self.timer.invalidate()
cell.timerTime.text = self.formattedTime(time: TimeInterval(item.seconds))
item.isStarted = false
还有我的数据模型:
class Timers: NSObject, Codable
var name = ""
var id = ""
var seconds = 0
var editSeconds = 0
var isStarted = false
func toggle()
isStarted = !isStarted
任何我的手机代码:
class TimerTableViewCell: UITableViewCell
@IBOutlet var timerLabel: UILabel!
@IBOutlet var timerTime: UILabel
@IBOutlet var startPauseButton: UIButton!
@IBOutlet var resetButton: UIButton!
如何同时管理多个计时器?当我使用 didSelectRowAt 时,只有同一个 Timer() 实例正在触发,因此多个计时器正在混合。如何划分多个计时器并使其工作?
【问题讨论】:
你能添加你的 TimerTableViewCell 定义吗? 我添加了,但它只有网点。 @alexsmith - 请不要发布同一问题的多个版本。编辑这个,并尝试更好地解释您正在尝试做什么,而不是发布“不起作用*”的部分代码。“计时器应用程序”是什么意思 ?您要完成什么?您的基本方法可能是错误的,因此您在此问题的多个帖子中获得的多个 cmets / 答案可能与解决开始的基本方法无关。 @alexsmith - 看看这个教程:raywenderlich.com/113835-ios-timer-tutorial ... 做了非常小的改动,看起来它可以做你想做的事。 【参考方案1】:这是一个完整的示例,基于 RayWenderlich.com 上的Fabrizio Brancati 的iOS Timer Tutorial
一切都通过代码完成(不需要@IBOutlet
或@IBAction
连接),因此只需创建一个新的UITableViewController
并将其自定义类分配给ExampleTableViewController
:
ExampleTableViewController.swift
//
// ExampleTableViewController.swift
// MultipleTimers
//
// Created by Don Mag on 5/12/20.
//
import UIKit
class ExampleTableViewController: UITableViewController
let cellID: String = "TaskCell"
var taskList: [Task] = []
var timer: Timer?
override func viewDidLoad()
super.viewDidLoad()
// -1 means use default 2-hours
let sampleData: [(String, Double)] = [
("First (2 hours)", -1),
("Second (2 hours)", -1),
("Third (10 seconds)", 10),
("Fourth (30 seconds)", 30),
("Fifth (1 hour 10 minutes)", 60 * 70),
("Sixth (2 hours)", -1),
("Seventh (45 minutes)", 60 * 45),
("Eighth (2 hours)", -1),
("Ninth (1 hour 10 minutes)", 60 * 70),
("Tenth (2 hours)", -1),
("Eleventh (45 minutes)", 60 * 45),
("Thirteenth (2 hours)", -1),
("Fourteenth (2 minutes)", 60 * 2),
("Fifthteenth (11 minutes)", 60 * 11),
("Sixteenth (2 hours)", -1),
]
sampleData.forEach (s, t) in
let task = Task(name: s, targetTime: t)
self.taskList.append(task)
tableView.register(TaskCell.self, forCellReuseIdentifier: cellID)
createTimer()
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int
return 1
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return taskList.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! TaskCell
cell.task = taskList[indexPath.row]
return cell
// MARK: - Timer
extension ExampleTableViewController
func createTimer()
if timer == nil
let timer = Timer(timeInterval: 1.0,
target: self,
selector: #selector(updateTimer),
userInfo: nil,
repeats: true)
RunLoop.current.add(timer, forMode: .common)
timer.tolerance = 0.1
self.timer = timer
func cancelTimer()
timer?.invalidate()
timer = nil
@objc func updateTimer()
guard let visibleRowsIndexPaths = tableView.indexPathsForVisibleRows else
return
for indexPath in visibleRowsIndexPaths
if let cell = tableView.cellForRow(at: indexPath) as? TaskCell
cell.updateTime()
TaskCell.swift
//
// TaskCell.swift
// MultipleTimers
//
// Created by Don Mag on 5/12/20.
//
import UIKit
class TaskCell: UITableViewCell
let taskNameLabel: UILabel =
let v = UILabel()
v.textAlignment = .center
return v
()
let timerLabel: UILabel =
let v = UILabel()
v.textAlignment = .center
v.font = UIFont.monospacedDigitSystemFont(ofSize: 17.0, weight: .medium)
return v
()
let actionButton: UIButton =
let v = UIButton()
v.setTitle("Start", for: [])
v.setTitleColor(.lightGray, for: .highlighted)
v.setTitleColor(.darkGray, for: .disabled)
v.backgroundColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0)
return v
()
let resetButton: UIButton =
let v = UIButton()
v.setTitle("Reset", for: [])
v.setTitleColor(.lightGray, for: .highlighted)
v.setTitleColor(.darkGray, for: .disabled)
v.backgroundColor = .red
return v
()
let buttonStack: UIStackView =
let v = UIStackView()
v.axis = .horizontal
v.distribution = .fillEqually
v.spacing = 16
return v
()
var task: Task?
didSet
taskNameLabel.text = task?.name
timerLabel.text = "0"
setState()
updateTime()
func setState() -> Void
switch task?.state
case .running:
actionButton.setTitle("Pause", for: [])
actionButton.isEnabled = true
case .paused:
if task?.elapsedTime == 0
actionButton.setTitle("Start", for: [])
actionButton.isEnabled = true
else
actionButton.setTitle("Resume", for: [])
actionButton.isEnabled = true
default: // .completed
actionButton.setTitle("", for: [])
actionButton.isEnabled = false
func updateTime()
guard let task = task else
return
var t: Double = 0
if task.state == .paused
t = task.targetTime - task.elapsedTime
else
t = task.targetTime - (Date().timeIntervalSince(task.creationDate) + task.elapsedTime)
let tm = Int(max(t, 0))
let hours = tm / 3600
let minutes = tm / 60 % 60
let seconds = tm % 60
let s = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
timerLabel.text = s
timerLabel.textColor = tm > 0 ? .black : .red
if tm == 0
task.state = .completed
setState()
@objc
func buttonTapped(_ sender: UIButton) -> Void
guard let s = sender.currentTitle, let task = task else return
switch s
case "Start", "Resume":
task.state = .running
task.creationDate = Date()
case "Pause":
task.state = .paused
task.elapsedTime += Date().timeIntervalSince(task.creationDate)
case "Reset":
task.state = .paused
task.elapsedTime = 0
default:
break
setState()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
required init?(coder: NSCoder)
super.init(coder: coder)
commonInit()
func commonInit() -> Void
[buttonStack, resetButton, actionButton, timerLabel, taskNameLabel].forEach
$0.translatesAutoresizingMaskIntoConstraints = false
buttonStack.addArrangedSubview(actionButton)
buttonStack.addArrangedSubview(resetButton)
contentView.addSubview(buttonStack)
contentView.addSubview(taskNameLabel)
contentView.addSubview(timerLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
buttonStack.topAnchor.constraint(equalTo: g.topAnchor),
buttonStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
buttonStack.widthAnchor.constraint(equalToConstant: 280.0),
taskNameLabel.topAnchor.constraint(equalTo: buttonStack.bottomAnchor, constant: 8.0),
taskNameLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
taskNameLabel.widthAnchor.constraint(equalTo: buttonStack.widthAnchor),
timerLabel.topAnchor.constraint(equalTo: taskNameLabel.bottomAnchor, constant: 8.0),
timerLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
timerLabel.widthAnchor.constraint(equalTo: buttonStack.widthAnchor),
timerLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
actionButton.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
resetButton.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
Task.swift
//
// Task.swift
// MultipleTimers
//
// Created by Don Mag on 5/12/20.
//
import Foundation
enum TimerState
case paused, running, completed
class Task
let name: String
var creationDate = Date()
var elapsedTime: Double = 0
var state: TimerState = .paused
var targetTime: Double = 60 * 60 * 2 // default 2 hours
init(name: String, targetTime: Double)
self.name = name
if targetTime != -1
self.targetTime = targetTime
这是它在运行时的样子:
【讨论】:
非常感谢!【参考方案2】:嗯,只保留了一个 Timer 实例,所以当你不想要它时,可以用其他计时器替换它。
最好将行而不是单元格对象传递给startPauseTimer
,因为单元格通常会被重用。然后您可以通过func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
处理所需的单元格并更改其文本。
让我们创建TimerModel
类:
class TimerModel
let timer: Timers
var actualTimer: Timer?
init(_ timer: Timers)
self.timer = timer
self.actualTimer = nil
那么假设你有timers = [TimerModel(Timers()), TimerModel(Timers())]
选择一行:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! TimerTableViewCell
let item = timers[indexPath.row]
item.timer.toggle()
print(item)
startPauseTimer(for: indexPath.row)
开始暂停:
func startPauseTimer(for row: Int)
let item = self.timers[row].timer
if !item.isStarted
self.timers[row].actualTimer?.invalidate()
else
self.timers[row].actualTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) [weak self] timer in
guard let self = self else return
if let cell = self.tableView.cellForRow(at: IndexPath(row: row, section: 0))
item.seconds -= 1
cell.textLabel?.text = "\(TimeInterval(item.seconds))"
if item.seconds < 1
self.timers[row].actualTimer?.invalidate()
cell.textLabel?.text = "\(TimeInterval(item.seconds))"
item.isStarted = false
如果您想删除一行(在用户或以编程方式删除行时调用):
func onRemove(at row: Int)
timers[row].actualTimer?.invalidate()
timers[row].actualTimer = nil
timers.remove(at: row)
有关编辑 UITableView 的信息,请参阅苹果文档: https://developer.apple.com/documentation/uikit/uitableview
将表格置于编辑模式如果您想通过与单元格交互进行编辑 如果您想以编程方式编辑行,请插入、删除和移动行和部分。【讨论】:
我无法将 var timer 移动到 Timers 类,因为它不符合 Codable 协议。 即使有一个定时器数组也无济于事,因为跟踪项目列表的修改会很乏味。即当添加/删除一个项目时,也应该添加/删除相应的计时器。 它会起作用的。为了方便起见,它可以包装在其他类中。 能否提供示例代码?如何在数组中添加和删除计时器?以及如何将其包装在其他类中? @alexsmith,好的,请参阅最新的编辑。这应该说明方法。以上是关于如何在一个 viewController 中管理多个计时器?的主要内容,如果未能解决你的问题,请参考以下文章