使用UIViewCells创建计时器

时间:2015-10-10 17:57:55

标签: ios swift uitableview uiview timer

我正在研究计时器应用。如此处所见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
}

}

由于

1 个答案:

答案 0 :(得分:0)

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

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

您的观点,表格查看单元格,由于效率原因而被重复使用,这对每个人都有益。

你有一个数组中的计时器,代表你的模型。您正在使用cellForRowAtIndexPath:在控制器中更新模型中的视图,除了一件事情之外,这很棒。

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

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

一旦定时器与视图分离,当通过dequeueReusableCellWithIdentifier回收视图时,您就不会遇到模型数据混淆的问题。

可重复使用单元格的挑战将是确保单元格在每次重用时都显示正确的模型数据。在新单元格完全渲染之前,单元格可以暂时可见,您可能需要临时隐藏新单元格,直到数据完全更新为止。

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