【问题标题】:Creating a timer with UIViewCells使用 UIViewCells 创建计时器
【发布时间】:2015-10-10 17:57:55
【问题描述】:

我正在开发一个计时器应用程序。如此处所示Timer App。此计时器应用程序与自定义 UIView 单元一起使用。一切正常,直到您创建的计时器(单元格)比显示的多。例如,假设我添加了 15 个计时器,其中 10 个显示(显示在屏幕上),而 5 个不显示。如果我启动其中一个计时器,然后向下滚动以显示其余计时器,您将看到其中一个计时器已经在运行,而无需手动启动它。好像它被复制了一样。我确实知道系统会重用单元格。我怎样才能避免这种情况?我怎样才能使计时器无论有多少都单独工作?

Error Screenshot

TimerTableViewCell: UITableViewCell,UITextFieldDelegate {

// MARK: - Properties
var time = 30
var displayTime = 30
var counterRun = NSTimer()
var startTime = NSTimeInterval()
var currentTime = NSTimeInterval()

@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var startLabel: UIButton!
@IBOutlet weak var numberText: UITextField!
@IBOutlet weak var buttonImage: UIButton!

// MARK: - Defaults
override func awakeFromNib() {
    super.awakeFromNib()
    /// Initialization code
    numberText.delegate = self

}
override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    /// Configure the view for the selected state
}

// MARK: Delegate Function

func textFieldShouldReturn(textField: UITextField) -> Bool {
    /// Hide the keyboard
    numberText.resignFirstResponder()
    return true
}


// MARK: Actions

@IBAction func startButton(sender: UIButton) {
    /// Makes time run
    if displayTime == 0 {
        time = 30
        displayTime = 30
        timeLabel.text = "\(displayTime)"
        counterRun = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timeRunner", userInfo: nil, repeats: true)
        startTime = NSDate.timeIntervalSinceReferenceDate()
        startLabel.selected = true
        startLabel.setImage(UIImage(named: "Stop"), forState: .Selected)
    }
    else if startLabel.selected == true {
        startLabel.selected = false
        startLabel.setImage(UIImage(named: "Start"), forState: .Normal)
        counterRun.invalidate()
        time = displayTime
    }
    else {
        startLabel.selected = true
        startLabel.setImage(UIImage(named: "Stop"), forState: .Selected)
        counterRun = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timeRunner", userInfo: nil, repeats: true)
        startTime = NSDate.timeIntervalSinceReferenceDate()
    }
}

func timeRunner(){
    currentTime = NSDate.timeIntervalSinceReferenceDate()

    let elapsedTime: NSTimeInterval = currentTime - startTime
    ///calculate the minutes in elapsed time.
    let minutes = UInt8(elapsedTime / 1)
    let minutesInt = Int(minutes)
    displayTime = time - minutesInt
    timeLabel.text = "\(displayTime)"

    timeStopper()
}

func timeStopper(){
    if displayTime <= 0 {
        startLabel.selected = false
        startLabel.setImage(UIImage(named: "Start"), forState: .Normal)
        counterRun.invalidate()
    }
}

@IBAction func refreshButton(sender: UIButton) {
    startLabel.selected = false
    startLabel.setImage(UIImage(named: "Start"), forState: .Normal)
    time = 30
    displayTime = 30
    timeLabel.text = "\(displayTime)"
    counterRun.invalidate()
}

@IBAction func changePhoto(sender: UIButton) {
    let duckPhoto = UIImage(named: "Duck")
    let swanPhoto = UIImage(named: "Swan")

    /// Changes the photo of the cell.
    if buttonImage.selected == false {
        buttonImage.selected = true
        buttonImage.setImage(swanPhoto, forState: .Selected)
    }
    else if buttonImage.selected == true {
        buttonImage.selected = false
        buttonImage.setImage(duckPhoto, forState: .Normal)
    }
}

}

这是 UITableViewController

class TimerTableViewController: UITableViewController {


// MARK: - Properties

var timers = [Time]()

override func viewDidLoad() {
    super.viewDidLoad()

    /// Uncomment the following line to preserve selection between presentations
    /// self.clearsSelectionOnViewWillAppear = false

    /// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
     self.navigationItem.leftBarButtonItem = self.editButtonItem()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    /// Dispose of any resources that can be recreated.
}

// MARK: - Table view data source


override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return timers.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let identifier = "TimerCell"
    let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! TimerTableViewCell

    let time = timers[indexPath.row]

    cell.timeLabel.text = "\(time.time)"
    cell.buttonImage.setImage(time.photo, forState: .Normal)
    return cell
}



/// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    /// Return false if you do not want the specified item to be editable.
    return true
}



/// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        /// Delete the row from the data source
        timers.removeAtIndex(indexPath.row)

        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    } else if editingStyle == .Insert {
        /// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }    
}

/// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

}

/// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    /// Return false if you do not want the item to be re-orderable.
    return true
}


// MARK: Actions

@IBAction func addButton(sender: UIBarButtonItem) {

    let photo = UIImage(named: "Duck")
    let time = Time(time: 30, photo: photo!)

    let newIndexPath = NSIndexPath(forRow: timers.count, inSection: 0)
    timers.append(time)
    tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)

}

}

以及制作计时器对象的类

class Time {
let time: Int
let photo: UIImage

init(time: Int, photo: UIImage){
    self.time = time
    self.photo = photo
}

}

谢谢

【问题讨论】:

  • 听起来你让初学者在细胞和细胞重用方面犯了错误。也不清楚你在用计时器做什么。你是在说NSTimers 吗?您是否为每个单元格创建一个?他们在做什么?他们调用什么方法,当您说“显示计时器”时,您是什么意思?发布代码的相关部分,否则我们将无法为您提供帮助。
  • 我确信我犯了初学者的错误。 “显示”意味着它正在屏幕上显示。不,我不会为每个单元格创建一个。我将更新我的问题并添加代码。感谢回复 !期待有帮助的答案!
  • @DuncanC 我刚刚添加了代码。谢谢
  • 好的,您已经发布了自定义 UITableViewCell 子类的代码。现在您的表格视图数据源方法的代码如何?(cellForRowAtIndexPath 等)
  • @DuncanC 在那里,我已经添加了我拥有的所有东西。

标签: ios swift uitableview uiview timer


【解决方案1】:

与其像我最初回答的那样覆盖表格视图单元格的重用行为,不如使用它。原因是这是您需要在应用程序中经常使用的东西,它也符合 iOS 中主要使用的 MVC 范例。

表格单元格只是一个视图,因此独立于 MVC 范例中的 模型

出于效率的原因,您的视图(表格视图单元格)被重复使用,这对每个人都有好处。

您将计时器放在一个数组中,它代表您的模型。您正在使用 cellForRowAtIndexPath: 从控制器中的模型更新视图,这很好,除了一件事。

您所犯的错误是您将模型数据存储在表格视图单元格视图TimerTableViewCell 中,而应该仅限于在模型中呈现数据。通过在视图中存储模型数据,您会遇到重用视图时模型数据不正确的问题。

NSTimer 需要从您的单元格视图移动到您的Time 类,以使其独立于您的视图。当您创建新的Time 对象时,它将成为实例成员。然后,您的 Time 对象数组将在您创建新计时器时独立运行计时器。

一旦定时器从视图中分离出来,就不会出现通过dequeueReusableCellWithIdentifier回收视图时模型数据混淆的问题。

可重用单元的挑战将是确保单元在每次重用时都显示正确的模型数据。在新单元格完全呈现之前,单元格可能会暂时可见,您可能需要暂时隐藏新单元格,直到数据完全更新。

这可能不是您一直在寻找的快速解决方案,但它将极大地帮助您实现 MVC。

【讨论】:

  • 这似乎不起作用,当我尝试添加新计时器时它给我一个错误! :( 还有其他建议吗?
  • 当我按下添加按钮时,应用程序崩溃并说找到 nil,当我试图打开一个可选的。它把我带到了这条线上。 cell.timeLabel.text = "(time.time)"
  • 我已经更新了我的答案以从 XIB 文件中检索您的单元格,如果这是您定义单元格视图的方式。
  • 谢谢,会检查是否有效,现在上飞机,会尽快通知您。非常感谢您的帮助。
  • 没问题,约翰。答案是让您的表格视图单元格位于TimerCell.xib 中。我希望这适用于您的应用。
猜你喜欢
  • 2012-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
相关资源
最近更新 更多