Не удается включить таймер
Я хочу сделать так, чтобы при нажатии на кнопку включался таймер, отсчитывал секунды, а после остановки, при повторном воспроизведении, начинал отсчет с той секунды, на которой остановился при нажатии на паузу. Но сейчас у меня никаких изменений со временем не происходит, хотя изображение на кнопке меняется. Код:
//MusicViewController
private var second64: Int64?
private var second = TimeInterval()
private var time: String?
//В этом методе мы считаем секунды
@objc private func countSeconds( _ : TimeInterval) -> TimeInterval{
if second > 0.0{
second -= 1.0
second64 = Int64(second)
time = TimeFormatter().convertTimeToString(second64)
}
return second
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Воспроизводим длительность аудиозаписи в основном потоке
second64 = audio.duration
time = TimeFormatter().convertTimeToString(second64)
var timer = Timer()
if button.title(for: .normal) == "▶️"{
if button.isSelected{
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0){
timer = self.setTimer()
cell.durationLabel.text = self.time
}
}
else if button.title(for: .selected) == "⏸" {
if button.isSelected{
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0){
timer.invalidate()
cell.durationLabel.text = self.time
}
}
}
}
}
else{
cell.singerLabel.text! = ""
cell.titleLabel.text! = ""
}
}
//Метод в протоколе для взаимодействия с презентером.
func setTimer () -> Timer{
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countSeconds(_:)), userInfo: nil, repeats: true)
print(second)
return timer
}
}
class TimeFormatter{
private var seconds = TimeInterval()
//Метод, преобразующий время в нужный формат
private func timeFormatter (_ : TimeInterval) -> DateComponentsFormatter{
let formatter = DateComponentsFormatter()
// Длительность будет отображаться как 1:00:00
if seconds > 59.59{
formatter.allowedUnits = [.hour, .minute, .second]
}
else{
formatter.allowedUnits = [.minute, .second]
}
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
return formatter
}
//Метод, передающий информацию о длительности записи в приложение
func convertTimeToString(_ number: Int64?) -> String?{
if let unwrappedNumber = number{
seconds = TimeInterval(integerLiteral: unwrappedNumber)
print(seconds)
}
let formatter = timeFormatter(seconds)
let timeString = formatter.string(from: seconds)
return timeString
}
}
UPD. Теперь мой код выглядит так: //MusicViewController
private var index = Int()
private var posts: [Post] = []
private var timer: Timer?
private var playingCell: MusicTableViewCell?{
let count = store!.getPostsCount()
for i in 0...count-1{
index = i
let post = store!.getPost(for: index)
posts.append(post)
}
let indexPath = IndexPath(row: index, section: 0)
return tableView.cellForRow(at: indexPath) as? MusicTableViewCell
private func reset(){
guard let cell = playingCell, let button = cell.playButton, let post = store?.getPost(for: index), let attachments = post.attachments, let audio = attachments[index].audio else {return}
button.isSelected = false
second64 = audio.duration
cell.second = cell.makeTimeIntervalFromInt(second64)
}
//Метод останавливает музыку на текущем моменте private func pauseMusic() {
guard let cell = playingCell, let playbutton = cell.playButton else {return}
playbutton.isSelected = false
timer?.invalidate()
}
} private var second64: Int64? //private let second = TimeInterval(integerLiteral: second64!)
extension MusicViewController: UITableViewDataSource, MusicTableViewCellDelegate{
func musicShouldStartPlaying(_ cell: MusicTableViewCell) {
if cell !== playingCell{
musicShouldStopPlaying(cell)
}
cell.playButton.isSelected = true
timer = cell.setTimer()
}
func musicShouldStopPlaying(_ cell: MusicTableViewCell) {
if cell.second == 0.0 || cell !== playingCell{
cell.playButton.isSelected = false
pauseMusic()
reset()
}
else{
cell.playButton.isSelected = false
pauseMusic()
}
cell.playButton.isSelected = false
pauseMusic()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let button = cell.playButton!
button.actions(forTarget: button, forControlEvent: .touchUpInside)
cell.delegate = self
cell.index = indexPath.row
cell.second = cell.makeTimeIntervalFromInt(second64)
}
//MusicTableViewCell
var second64: Int64?
var index: Int!
var second = TimeInterval()
private var time: String?
weak var delegate: MusicTableViewCellDelegate?
func makeTimeIntervalFromInt( _ : Int64?) -> TimeInterval{
if let currentSecond = second64{
second = TimeInterval(integerLiteral: currentSecond)
}
return second
}
//Протокол взаимодействия View с презентером protocol ViewProto: class {
//Метод, вызывающий презентер во View
func setPresenter(_ presenter: PresenterProto)
//Метод, обновляющий посты
func updateData()
// func reset()
}
//Протокол взаимодействия презентера с View protocol PresenterProto: class {
// Метод, вызывающий View в презентере
func viewLoaded(with view: ViewProto)
//Метод, позволяющий узнать количество постов в массиве
func getPostsCount() -> Int
//Показывает содержание конкретного поста
func getPost(for index: Int) -> Post
}
protocol MusicTableViewCellDelegate: class{ func musicShouldStartPlaying(_ cell: MusicTableViewCell)
func musicShouldStopPlaying(_ cell: MusicTableViewCell)
//func reset(_ cell: MusicTableViewCell)
}
'''
Ответы (1 шт):
Вот пример работы с возобновляемым таймером.
Дополнение
Вам нужно обновлять время в методе countSeconds как показано ниже
class ViewController: UIViewController {
@IBOutlet var playButton: UIButton!
@IBOutlet var durationLabel: UILabel!
private var seconds = TimeInterval()
private var timeString = ""
private var timer: Timer?
private var isPlaying = false
override func viewDidLoad() {
super.viewDidLoad()
playButton.setTitle("Play", for: .normal)
seconds = 100
}
@IBAction func playButtonTouchUpInside(_ sender: UIButton) {
togglePlayback()
playButton.setTitle(isPlaying ? "Stop" : "Play", for: .normal)
}
@objc private func countSeconds(_ sender: Timer) {
if seconds > 0.0 {
seconds -= 1.0
timeString = TimeFormatter.convertTimeToString(seconds)
DispatchQueue.main.async {
self.durationLabel.text = self.timeString
}
}
}
func togglePlayback() {
isPlaying = !isPlaying
if isPlaying {
startTimer()
} else {
stopTimer()
}
}
func startTimer() {
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(countSeconds(_ :)),
userInfo: nil,
repeats: true)
}
func stopTimer() {
timer?.invalidate()
}
}
Дополнение 2
Задача показалась мне интересной, я полностью переписал реализацию автора вопроса и сделал тестовый проект с логикой переключения записей в списке (github)
Первоначально я перенес логику переключения и отслеживания времени в ячейки, однако это оказалось неверным решением из-за повторного использования ячеек при прокрутке и соответственно остановки таймера, работающего в выбранной ячейке, поэтому я сделал общий таймер во вью контроллере, и здесь столкнулся с тем, что обычный Timer тоже останавливается при прокрутке, так как запускается в основном цикле событий (main loop).
Поэтому я использовал реализацию фонового таймера отсюда
Замечания:
- иногда при возобновлении таймера в начале быстро проскакивает первая секунда
- неоптимальный вариант получения текущей ячейки для обновления времени в ней при большом списке
Класс ячейки содержит кнопку переключения и лейбл для вывода времени, внутри управляет переключением кнопки и делегирует события переключения во вью контроллер
class RecordDurationFormatter : DateComponentsFormatter {
override init() {
super.init()
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
unitsStyle = .positional
zeroFormattingBehavior = .pad
}
/// Метод, преобразующий время в нужный формат
func format(_ seconds: TimeInterval) -> String? {
// Длительность будет отображаться как 1:00:00
if seconds > 59.59 {
allowedUnits = [.hour, .minute, .second]
} else {
allowedUnits = [.minute, .second]
}
return string(from: seconds)
}
}
protocol RecordTableViewCellDelegate : class {
func recordTableViewCellShouldStartPlaying(_ cell: RecordTableViewCell)
func recordTableViewCellShouldPausePlaying(_ cell: RecordTableViewCell)
}
class RecordTableViewCell: UITableViewCell {
@IBOutlet var playButton: UIButton!
@IBOutlet var timeLabel: UILabel!
private let stoppedTitle = "▶️"
private let playingTitle = "⏸"
let formatter = RecordDurationFormatter()
private var timer: Timer?
var recordIndex: Int!
var isPlaying: Bool = false {
didSet {
togglePlayButtonTitle(playing: isPlaying)
}
}
var seconds: TimeInterval = 0 {
didSet {
updateTimeLabelText()
}
}
weak var delegate: RecordTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
private func setup() {
togglePlayButtonTitle(playing: false)
}
// MARK: UI Update
private func togglePlayButtonTitle(playing: Bool) {
playButton.setTitle(playing ? playingTitle : stoppedTitle, for: .normal)
}
func updateTimeLabelText() {
DispatchQueue.main.async {
self.timeLabel.text = self.formatter.format(self.seconds)
}
}
// MARK: UI Events
@IBAction func playButtonTouchUpInside(_ sender: UIButton) {
isPlaying = !isPlaying
if isPlaying {
delegate?.recordTableViewCellShouldStartPlaying(self)
} else {
delegate?.recordTableViewCellShouldPausePlaying(self)
}
}
}
Вью контроллер содержит таблицу, управляет таймером и переключением между записями
class Record {
var isPlaying = false
var duration: TimeInterval = 100
lazy var currentSeconds = duration
func reset() {
isPlaying = false
currentSeconds = duration
}
func countDown() -> TimeInterval {
if currentSeconds > 0 {
currentSeconds -= 1.0
}
return currentSeconds
}
}
class RecordsViewController: UIViewController {
@IBOutlet var tableView: UITableView!
private var seconds = TimeInterval()
/// Using background timer because common Timer is getting suspended
/// on table view scroll (because of schedule on main loop)
private var timer = RepeatingTimer(timeInterval: 1)
private var records: [Record] = []
private var playingRecord: Record?
// TODO: maybe optimize because is gets called by timer via playingCell every second
private var playingRecordIndex: Int? {
records.firstIndex { $0 === playingRecord }
}
private var playingCell: RecordTableViewCell? {
guard let index = playingRecordIndex else {
return nil
}
let indexPath = IndexPath(row: index, section: 0)
return tableView.cellForRow(at: indexPath) as? RecordTableViewCell
}
override func viewDidLoad() {
super.viewDidLoad()
let nibName = String(NSStringFromClass(RecordTableViewCell.self).split(separator: ".").last!)
self.tableView.register(UINib(nibName: nibName, bundle: nil), forCellReuseIdentifier: "cell")
for _ in 0..<100 {
records.append(Record())
}
}
// MARK: Playback
/// Resume or start record
func startRecord(with index: Int) {
let record = records[index]
record.isPlaying = true
playingRecord = record
startTimer()
}
func stopPlayingNowRecord() {
guard let record = playingRecord, let cell = playingCell else {
return
}
record.reset()
cell.seconds = record.duration
cell.isPlaying = false
stopTimer()
}
func pausePlayingNowRecord() {
guard let record = playingRecord else {
return
}
record.isPlaying = false
stopTimer()
}
// MARK: Timer
@objc private func countSeconds() {
guard let record = self.playingRecord else {
return
}
seconds = record.countDown()
DispatchQueue.main.async {
guard let cell = self.playingCell else {
return
}
cell.seconds = self.seconds
}
}
private func startTimer() {
if timer.eventHandler == nil {
timer.eventHandler = {
self.countSeconds()
}
}
timer.resume()
}
private func stopTimer() {
timer.suspend()
}
}
extension RecordsViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
records.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RecordTableViewCell
cell.delegate = self
cell.recordIndex = indexPath.row
let record = records[indexPath.row]
cell.seconds = record.currentSeconds
cell.isPlaying = record.isPlaying
return cell
}
}
extension RecordsViewController : RecordTableViewCellDelegate {
func recordTableViewCellShouldStartPlaying(_ cell: RecordTableViewCell) {
if cell !== playingCell {
// stop previous record if any
stopPlayingNowRecord()
}
startRecord(with: cell.recordIndex)
}
func recordTableViewCellShouldPausePlaying(_ cell: RecordTableViewCell) {
pausePlayingNowRecord()
}
}
