3

I'm querying the createdAt date from several objects in Parse. I want to create a 24 hour (or 48/72, etc.) countDown timer that counts down from when the object was created to 24 hours later. (I'm also then formatting it, and displaying it on a cell UILabel.)

Example: If an object was created at 19:34:33, I want it to expire 24 hours after that (or how ever many hours i specify after it was created). Ultimately showing on the UILabel the hours left until the object expires.

Currently, I'm retrieving when it was created making it repetitively count down from when it was created.

However, I want to make the logic so that it takes when the object was created and then shows how many hours are left until the 24 hours or 48 hours, 72 hours, etc are up.

EDIT

Thanks to @pulsar I added a few more lines of code to the description below. The problem now is that I can only retrieve and correctly countDown the createdAt date and time for 1 object, because only the first object is queried, making all the other objects have the same expiration countDown timer in their respective indexPath.row as the first object in Parse.

I can't figure out how to add all the objects so that they all have their own respective countDown expiration times that is triggered by the expiresAt function.

Here's how i'm querying it and formatting it (in the viewDidLoad): This is the question I asked that help me format the dates: Swift countDown timer Logic

Please see the comments in the code!

            var createdAt = object.createdAt
            if createdAt != nil {


                 //assuming this is where i have to set expiration logic?

                let calendar = NSCalendar.currentCalendar()
                let comps = calendar.components([.Hour, .Minute, .Second], fromDate: createdAt as NSDate!)
                let hours = comps.hour  * 3600
                let minutes = comps.minute * 60
                let seconds = comps.second

                 //I'm adding these two lines below but not sure what to do with them considering I need to add all the objects to an array that will then display it on indexPath.row(s)

                   let twentyFourHours = NSTimeInterval(60 * 60 * 24)
                    self.expiresAt = NSDate(timeInterval: twentyFourHours, sinceDate: object.createdAt!!)


                self.timerInt.append(hours + minutes + seconds)
                //What do i append to the timerInt array? How can i append the objects while triggering the the expiresAt function? 

           } 

Here's my countDown function:

               func countDown() {

        //timerInt is an array where I'm storing the Int values.
        for i in 0 ..< timerInt.count {

            let hours = timerInt[i] / 3600 
          //have to somehow add the expiresAt method while looping through each value [i]...?
            let minsec = timerInt[i] % 3600
            let minutes = minsec / 60
            let seconds = minsec % 60
       print(String(format: "%02d:%02d:%02d", hours, minutes, seconds))
          timerInt[i]--

           //Im assuming best practice would be to loop through the values in order to change the values/set the expiration time to each object (the expiresAt method). Any ideas of how and where I can add this in this loop so that it reflects the countDown I want to set?

        }

        self.tableView.reloadData()

}

Lastly, for my indexPath.row, I am formatting it and displaying it like this:

          override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
         let myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! cell

  //I'm formatting the hours, minutes, seconds. However I'm missing the expiresAt function and I have no clue as to where and how to include it... Should it be here or in the countDown() loop?  

     let hours = timerInt[indexPath.row] / 3600
     let minsec = timerInt[indexPath.row] % 3600
     let minutes = minsec / 60
     let seconds = minsec % 60


      myCell.secondLabel.text = String(format: "%02d:%02d:%02d", hours, minutes, seconds)


        return myCell
}

Ideas on how to set it to countdown 24/48/72 hours later from when it was created?

Any and all help is much appreciated!

Community
  • 1
  • 1
Lukesivi
  • 2,206
  • 4
  • 25
  • 43

2 Answers2

1

Sounds like what you need is to set the expiry date and then get the date components between the current date and the expiry date. Then you can use an NSTimer to refresh the display. (Don't forget to call NSTimer.invalidate() when you're done).

An example:

class YourViewController: UIViewController {

    var expiresAt: NSDate?

    func viewDidLoad() {
        // your parse logic here
        let twentyFourHours = NSTimeInterval(60 * 60 * 24)
        expiresAt = NSDate(timeInterval: twentyFourHours, sinceDate: createdAt)
        scheduleTimer()
    }

    func scheduleTimer() {
        NSTimer.scheduledTimerWithTimeInterval(1.0 / 30.0, target: self, selector: "tick:", userInfo: nil, repeats: true)
    }

    @objc
    func tick(timer: NSTimer) {
        guard let expiresAt = expiresAt else {
            return
        }
        let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
        if let components = calendar?.components([.Hour, .Minute, .Second], fromDate: NSDate(), toDate: expiresAt, options: []) {
            print(formatDateComponents(components))
        }
    }

    func formatDateComponents(components: NSDateComponents) -> String {
        let hours = components.hour
        let minutes = components.minute 
        let seconds = components.second
        return "\(hours):\(minutes):\(seconds)"
    }

}

You could also make your life much easier by using a structure to store the date components rather than doing that complicated parsing of your timerInt strings.

struct Time {
    let hours: String
    let minutes: String
    let seconds: String
}
Binary Pulsar
  • 1,209
  • 10
  • 17
  • 1
    Thanks for the answer. Where would I place these functions? Within the viewDidLoad/where i'm querying? In the class itself? What do I do with the existing code? – Lukesivi Oct 29 '15 at 15:50
  • 1
    You could copy and paste the methods into your view controller if you want. You just need to set the expiresAt date before calling `scheduleTimer()`. – Binary Pulsar Oct 29 '15 at 15:52
  • 1
    I'm relatively new to Swift and I'm having difficulties understanding the code. Is there a way that I can change the function I have above where I'm querying the data from Parse (1st code snippet) and then make use of the `createdAt` column from Parse in order to count down from there? – Lukesivi Oct 29 '15 at 15:59
  • 1
    `createdAt` doesn't really matter once you've worked out `expiresAt`. My advice would be to make an instance variable for `expiresAt` (outside of your method) and set that where you are querying the data from Parse. Once that is set you can call `scheduleTimer()` and every `tick` uses the `expiriesAt` and the current date to work out how much time is left. Does that help? – Binary Pulsar Oct 29 '15 at 16:09
  • 1
    What do I set the `expiresAt` variable to where i'm querying the data from Parse? Thanks for the patience. – Lukesivi Oct 29 '15 at 17:10
  • 1
    Using the code you sent, i get a bunch of `init` and initializers I don't understand when trying it initialize it... I really rather not use code I don't understand even if it works. Is there a specific method I need to call when creating the expiresAt? If so, when i have that method initialized in the expiresAt, how do I plug that in to where i'm querying? – Lukesivi Oct 29 '15 at 17:19
  • 1
    I've changed my answer to (hopefully) work in the context of your view controller – Binary Pulsar Oct 30 '15 at 09:01
  • 1
    When I print to the logs this works. But what do I call to set up my `cellForRowAtIndexPath`? I'm trying to do this: ` myCell.secondLabel.text = "\(self.formatDateComponents(components))"` but the error i'm getting is that it can't find components. In other words, Im trying to format it in the cellForRow, and then print it to a UILabel. – Lukesivi Oct 30 '15 at 09:14
  • 1
    If it works when printing then it will work on the cell. You must not be calling it from the `tick:` method. Are you setting up your date components correctly? You could use `UITableView.visibleCells` to find your cell from the tick method? – Binary Pulsar Oct 30 '15 at 09:19
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/93782/discussion-between-rinyfo4-and-pulsar). – Lukesivi Oct 30 '15 at 09:24
  • 1
    I have it printing now. But it doesn't print the indexPath.row. It prints only the first value it retrieves. I'm attempting to do something like this (even though it's clearly wrong, just as an example): `myCell.secondLabel.text = "\(self.formatDateComponents(components!)[indexPath.row])"` – Lukesivi Oct 30 '15 at 10:09
  • 1
    hey, any thoughts? @pulsar – Lukesivi Oct 31 '15 at 11:05
1
//: Playground - noun: a place where people can play

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import UIKit

class MyView: UIView {
    weak var l1: UILabel?
    weak var l2: UILabel?
    weak var l3: UILabel?

    let validFor: NSTimeInterval
    var validTo: NSDate = NSDate()

    lazy var timer: NSTimer = NSTimer(timeInterval: self.validFor, target: self, selector: "done", userInfo: nil, repeats: false)

    init(validFor: NSTimeInterval) {
        self.validFor = validFor
        super.init(frame: CGRect(x: 0, y: 0, width: 500, height: 100))
        validTo = timer.fireDate

        let ll1 = UILabel(frame: CGRect(x: 1, y: 1, width: 498, height: 30))
        ll1.text = "created at: \(NSDate())"
        self.addSubview(ll1)
        l1 = ll1

        let ll2 = UILabel(frame: CGRect(x: 1, y: 33, width: 498, height: 30))
        ll2.text = "valid to: \(validTo)"
        self.addSubview(ll2)
        l2 = ll2

        let ll3 = UILabel(frame: CGRect(x: 1, y: 66, width: 498, height: 30))
        ll3.text = "valid for next: \(validTo.timeIntervalSinceNow) second"
        self.addSubview(ll3)
        l3 = ll3

        NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
    }

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

    // when MyView expires it trigers this function
    // and give me a chance to update UI
    func done() {
        // update UI
        dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
            self.l2?.text = "     EXPIRED"
            self.l3?.text = ""
            if let text1 = self.l1?.text,
                let text2 = self.l2?.text,
                let text3 = self.l3?.text
            {
                print("")
                print(text1, text2, text3)

            }
        })
    }
    func updateState() {
        // periodically updating UI on request
        if timer.valid {
            let v = validTo.timeIntervalSinceNow

            // update UI
            dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
                self.l3?.text = "valid for next: \(v) second"
                if let text1 = self.l1?.text,
                    let text2 = self.l2?.text,
                    let text3 = self.l3?.text
                {

                    print(text1, text2, text3)


                }
            })
        }
    }
}


let c = MyView(validFor: 10.0) // in seconds

let queue = dispatch_queue_create("update", DISPATCH_QUEUE_SERIAL)

// periodic action tu update UI
// in regular intervals
// this is just for demonstration, avoid using use sleep
// in real application
dispatch_async(queue) { () -> Void in
    repeat {
        c.updateState()
        sleep(3)
    } while true
}

dispatch_async(dispatch_get_main_queue(), { () -> Void in
    print("the app is running and responding on user actions")
    print("MyView labels are updating 'automatically'\n")
})

print("playground continue ....\n")
user3441734
  • 16,722
  • 2
  • 40
  • 59
  • 1
    Thanks for the answer. How do I integrate this in my VC? – Lukesivi Nov 02 '15 at 12:54
  • 1
    every object now has its own life, so in your UI update cycles just use your own version of alive(), which meets your requirements. – user3441734 Nov 02 '15 at 13:08
  • 1
    My main issue is storing the `createdAt` variables in an array in order to access them and have the UI update based on the `indexPath.row`. I'm having trouble understanding your code. Do you mind adding an explanation? Is this purely for comparing NSDates? http://postimg.org/image/mp1g8frpr/ this is what i'm getting. The invalidate date and the createdAt date are the same. An explanation would be great :) thanks! – Lukesivi Nov 02 '15 at 13:36
  • i updated the example. run it in your playground. check, how the ui is updated 'automatically'. think about the code just as a very simplified base to show you an idea. – user3441734 Nov 07 '15 at 20:33
  • I don't see how I can implement this into my VC. It's overly complicated. Have you tried this in a project? I essentially need to compare many dates that are in an Array with the expiresAt function. – Lukesivi Nov 07 '15 at 20:47
  • you don't need to compare nothing. you don't need to change you VC. just add the life time to your objects and let them to update their own properties (UI). when creating the object, you must set the life time. that is all. – user3441734 Nov 07 '15 at 20:53
  • mind if I send you a gist? I still don't see how I can implement a lifetime. – Lukesivi Nov 07 '15 at 20:55
  • Heres the gist: https://gist.github.com/rinyfo4/dc0737174ae1def4dcab As you can see, I'm getting the createdAt from the `createdAt` column in Parse. Then I'm throwing that into a countDown function which I essentially need to put this "lifetime" timer on. Would that go there? – Lukesivi Nov 07 '15 at 21:00