UITableViews

时间:2015-10-15 18:52:16

标签: ios iphone swift uitableview timer

更新:此代码已使用以下答案建议的最新修补程序进行了更新。感谢所有帮助过的人。

我正在创建一个应用,我在UITableView中显示了几个定时器,如图Timers ListMenu所示。

我遵循MVC范例,并将模型,控制器和视图相互分离。所以我有

  • 定时器类,定时器在哪里。
  • UITableViewController
  • UITableViewCell
  • 一个UIViewController,我配置了计时器。

基本上一切正常,但我无法在每个单元格的标签中“显示”定时器。请注意,我是编程的6个月,这将是我的第一个具有UITableView的应用程序,并学习MVC的基础知识。

所以应用程序的工作原理是用户添加一个新的计时器,然后通过点击按钮“start”,计时器应该开始倒计时。这些是NSTimers。单击开始后,定时器将被触发并运行,但它们不会在标签上显示给用户。这就是我的问题所在。

如果有人有建议或者可以帮我搞清楚,我会非常感激。

这是我的代码。

计时器类:

@objc protocol Reloadable: class {
@objc optional func reloadTime()
}

class Timer {

// MARK: Properties
var time: Int
var displayTime: Int
var photo: UIImage
weak var dataSource: Reloadable?

// MARK: - Methods
init(time: Int, displayTime: Int, photo: UIImage){
    self.time = time
    self.displayTime = displayTime
    self.photo = photo
}

/// The Timer properties and Methods start from here ------

// MARK: - Timer Properties
var counterRun = NSTimer()
var colorRun = NSTimer()
var startTime = NSTimeInterval()
var currentTime = NSTimeInterval()

// MARK: - Timer Mothods
func startTimeRunner(){
    counterRun = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector:"timeRunner:", userInfo: nil, repeats: true)
    startTime = NSDate.timeIntervalSinceReferenceDate()
}

@objc func timeRunner(timer: NSTimer){
    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

     "reloadTime()" in the TimerTableVIewController.
    if let myDelegate = self.dataSource {
        myDelegate.reloadTime!()
    }
  }
}

TableViewController

class TimerTableViewController: UITableViewController, ButtonCellDelegate, Reloadable{

// MARK: Properties
var timers = [Timer]()

override func viewDidLoad() {
    super.viewDidLoad()

    /// Loads one timer when viewDidLoad
    let time = Timer(time: 30, displayTime: 30, photo: UIImage(named: "Swan")!)
    timers.append(time)

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

     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 cell = tableView.dequeueReusableCellWithIdentifier("TimerCell", forIndexPath: indexPath) as! TimerTableViewCell

    let time = timers[indexPath.row]

    cell.timeLabel.text = "\(time.displayTime)"
    cell.photo.image = time.photo

    /// Makes TimerTableViewController (self) as the delegate for TimerTableViewCell.
    if cell.buttonDelegate == nil {
        cell.buttonDelegate = self
    }
    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 {
        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: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

/// Unwind segue, the source is the MenuViewController.
@IBAction func unwindToTimerList(sender: UIStoryboardSegue){
    if let sourceViewController = sender.sourceViewController as? MenuViewController, time = sourceViewController.timer {

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

/// With the help of the delegate from TimerTableViewCell, when the "start" button is pressed, it will 
func cellTapped(cell: TimerTableViewCell) {
    let cellRow = tableView.indexPathForCell(cell)!.row
    let timer = timers[cellRow]

    timer.dataSource = self
    timer.startTimeRunner()
}

func reloadTime(){
    if self.tableView.editing == false {
        self.tableView.reloadData()
    }
  }
}

TableViewCell

protocol ButtonCellDelegate {
func cellTapped(cell: TimerTableViewCell)
}

class TimerTableViewCell: UITableViewCell, UITextFieldDelegate{

// MARK: Properties
@IBOutlet weak var startButtonOutlet: UIButton!
@IBOutlet weak var refreshButtonOutlet: UIButton!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var photo: UIImageView!

var buttonDelegate: ButtonCellDelegate?

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    /// UITextFieldDelegate to hide the keyboard.
    textField.delegate = self
}

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

    // Configure the view for the selected state
}
@IBAction func startButton(sender: UIButton) {
    if let delegate = buttonDelegate {
        delegate.cellTapped(self)
    }
}

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

和MenuViewController

class MenuViewController: UIViewController {

// MARK: Properties 

@IBOutlet weak var swanPhoto: UIImageView!
@IBOutlet weak var duckPhoto: UIImageView!
@IBOutlet weak var minsPhoto: UIImageView!
@IBOutlet weak var okButtonOutlet: UIButton!

var timer: Timer?
var photo: UIImage? = UIImage(named: "Swan")
var time: Int? = 30
var displayTime: Int? = 30


override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}

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


// MARK: Actions

@IBAction func swantButton(sender: UIButton) {
     photo = UIImage(named: "Swan")
}

@IBAction func duckButton(sender: UIButton) {
    photo = UIImage(named: "Duck")
}

@IBAction func okButton(sender: UIButton) {
}
@IBAction func cancelButton(sender: UIButton) {
    self.dismissViewControllerAnimated(true, completion: nil)
}

@IBAction func min60(sender: UIButton) {
    time = 60
    displayTime = 60
}

@IBAction func min30(sender: UIButton) {
    time = 30
    displayTime = 30
}

@IBAction func min15(sender: UIButton) {
    time = 15
    displayTime = 15
}

// MARK: Navegation

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if okButtonOutlet === sender {
        let photo = self.photo
        let time =  self.time
        let displayTime = self.displayTime

        timer = Timer(time: time!, displayTime: displayTime!, photo: photo!)
    }
}
}

3 个答案:

答案 0 :(得分:1)

您的主要问题是您将计时器的委托指定为视图控制器的新实例 - 委托需要是屏幕上的现有视图控制器。

就协议而言,你有正确的想法,但你的协议缺少一条关键信息 - reloadTime函数需要提供定时器实例作为参数。这将使视图控制器能够知道它正在处理哪个计时器,而不是必须重新加载整个表,这在视觉上没有吸引力。

protocol Reloadable {
    func reloadTime(timer:Timer)
}

func ==(lhs: Timer, rhs: Timer) -> Bool {
    return lhs.counterRun == rhs.counterRun
}

class Timer : Equatable {

// MARK: Properties
var time: Int
var displayTime: Int
var photo: UIImage
var delegate?

// MARK: - Methods
init(time: Int, displayTime: Int, photo: UIImage){
    self.time = time
    self.displayTime = displayTime
    self.photo = photo
}

// MARK: - Timer Properties
var counterRun = NSTimer()
var colorRun = NSTimer()
var startTime = NSTimeInterval()
var currentTime = NSTimeInterval()

// MARK: - Timer Mothods
func startTimeRunner(){
    counterRun = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector:"timeRunner:", userInfo: nil, repeats: true)
    startTime = NSDate.timeIntervalSinceReferenceDate()
}

@objc func timeRunner(timer: NSTimer){
    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

    print(displayTime)
    delegate?.reloadTime(self)

  }

}

为简洁起见,我只会显示您需要更改的表视图控制器方法

override func viewDidLoad() {
    super.viewDidLoad()

    let time = Timer(time: 30, displayTime: 30, photo: UIImage(named: "Swan")!)
    time.delegate=self
    self.timers.append(time)

    self.navigationItem.leftBarButtonItem = self.editButtonItem()
}

@IBAction func unwindToTimerList(sender: UIStoryboardSegue){
    if let sourceViewController = sender.sourceViewController as? MenuViewController, time = sourceViewController.timer {

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

func reloadTime(timer:Timer){
    if let timerIndex=self.timers.indexOf(timer) {
        let indexPath=NSIndexPath(forRow:timerIndex, inSection:0)
        if let cell=self.tableView.cellForRowAtIndexPath(indexPath) as? TimerTableViewCell {
            cell.timeLabel.text = "\(timer.displayTime)"
        }
    }
} 

答案 1 :(得分:0)

以这种方式尝试:

在时间类中替换

var delegate: Reloadable = TimerTableViewController()

weak var dataSource: Reloadable?

func timeRunner(timer: NSTimer) {
...
if let myDelegate = self.dataSource {
    myDelegate.reloadTime()
}

var delegate: Reloadable = TimerTableViewController()指的是TimerTableViewController()

的单独实例

将来如果你有多个计时器,你会想要使用tableView.reloadRowsAtIndexPathstableView.reloadSections

答案 2 :(得分:0)

为了我自己的好奇心和一些快速的练习,我制定了自己的解决方案。我有计时器单元更新和维护状态按预期。我没有在单元格上设置图像,因为你已经找到了那个部分。这就是我做到的。任何人都可以随意纠正我的Swift,我主要在Obj-C工作。

这是我的UITableViewController子类

import UIKit

class TimerTable: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.registerClass(TimerTableViewCell.self, forCellReuseIdentifier: TimerTableViewCell.reuseIdentifier())

        self.startObserving()
    }

    // MARK: - Notifications

    func startObserving()
    {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "timerFired:", name: TimerTableDataSourceConstants.TimerTableDataSource_notification_timerFired, object: nil)
    }

    // MARK: - Table view data source / delegate

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

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return TimerTableViewCell.cellHeight()
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(TimerTableViewCell.reuseIdentifier(), forIndexPath: indexPath) as! TimerTableViewCell
        let time = TimerTableDataSource.sharedInstance.currentTimeForIndexPath(indexPath)
        cell.configureLabelWithTime("\(time)")

        return cell
    }

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        TimerTableDataSource.sharedInstance.toggleTimerForIndexPath(indexPath)
    }

    // MARK: - Imperatives

    func timerFired(note: AnyObject?){
        let ip = note?.valueForKey("userInfo")?.valueForKey(TimerTableDataSourceConstants.TimerTableDataSource_userInfo_timerIndexPath) as! NSIndexPath
        self.tableView.reloadRowsAtIndexPaths([ip], withRowAnimation: .Automatic)
    }

}

这是我的UITableViewCell子类

import UIKit

    // MARK: - Constants

struct TimerTableViewCellConstants {
    static let reuseIdentifier = "TimerTableViewCell_reuseIdentifier"
    static let cellHeight : CGFloat = 60.0
}


class TimerTableViewCell: UITableViewCell {

    var timerLabel = UILabel()

    // MARK: - Lifecycle
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.setup()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Setup
    func setup()
    {
        let label = UILabel()
        label.textAlignment = .Center
        self.addSubview(label)

        let leadingConstraint = NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: 0)
        let trailingConstraint = NSLayoutConstraint(item: label, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1.0, constant: 0)
        let topConstraint = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0)
        let bottomConstraint = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: 0)

        label.translatesAutoresizingMaskIntoConstraints = false;
        self.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])

        self.timerLabel = label
    }

    // MARK: - Imperatives

    func configureLabelWithTime(time: String)
    {
        self.timerLabel.text = time
    }

    // MARK: - Accessors

    class func reuseIdentifier() -> String
    {
        return TimerTableViewCellConstants.reuseIdentifier
    }

    class func cellHeight() -> CGFloat
    {
        return TimerTableViewCellConstants.cellHeight
    }

}

以下是我的计时器数据源

import UIKit

//NSNotificationCenter Constants
struct TimerTableDataSourceConstants {
    static let TimerTableDataSource_notification_timerFired = "TimerTableDataSource_notification_timerFired"
    static let TimerTableDataSource_userInfo_timerIndexPath = "TimerTableDataSource_userInfo_timerIndexPath"
}

class TimerTableDataSource: NSObject {
    //Datasource Singleton
    static let sharedInstance = TimerTableDataSource()

    var timerDict = NSMutableDictionary()

    // MARK: - Accessors

    func currentTimeForIndexPath(ip: NSIndexPath) -> Int {
        if let timerDataArray = timerDict.objectForKey(ip) as? Array<AnyObject>
        {
            return timerDataArray[1] as! Int
        }

        return 30
    }

    // MARK: - Imperatives

    func toggleTimerForIndexPath(ip: NSIndexPath)
    {
        if let timer = timerDict.objectForKey(ip) as? NSTimer{
            timer.invalidate()
            timerDict.removeObjectForKey(ip)
        }else{
            let timer = NSTimer(timeInterval: 1.0, target: self, selector: "timerFired:", userInfo: ip, repeats: true)
            NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
            timerDict.setObject([timer, 30], forKey: ip)
        }
    }

    func timerFired(sender: AnyObject)
    {
        let timer = sender as! NSTimer
        let indexPath = timer.userInfo as! NSIndexPath
        let timerDataArray = timerDict.objectForKey(indexPath) as! Array<AnyObject>
        var timeRemaining: Int = timerDataArray[1] as! Int

        if (timeRemaining > 0){
            timeRemaining--
            timerDict.setObject([timer, timeRemaining], forKey: indexPath)
        }else{
            timer.invalidate()
        }


        NSNotificationCenter.defaultCenter().postNotificationName(
            TimerTableDataSourceConstants.TimerTableDataSource_notification_timerFired,
            object: nil,
            userInfo: [TimerTableDataSourceConstants.TimerTableDataSource_userInfo_timerIndexPath : indexPath]
        )
    }
}