3

I have seen 1 other question like this but I didn't help. In my application, I want a timer to start counting when I press the start button. I also want to move a button from one side of the screen to another (when I click the start button). To add to that, I want another button to move from one side of the screen to another after a 1 second delay of clicking the start button. The problem is that even though I gave a different name for each timer (NS Timer), they are messing around with each other. After clicking the start button, the timer for counting the seconds and the first button who moves work fine, but after 1 second, the first button goes back to the beginning and starts over and the second button starts to move but then it does the same thing as the first button. The timer that counts the seconds still works fine though. Heres the code (BTW I use CADisplayLink to move the buttons):

var displayLink: CADisplayLink?
var displayLink1: CADisplayLink?
@IBOutlet var moving1outlet: UIButton!
@IBOutlet var moving2outlet: UIButton!
@IBOutlet var timerlabel: UILabel!
var timer = NSTimer()
var timer2 = NSTimer()
var startTime = NSTimeInterval()
@IBAction func startbutton(sender: UIButton) {
     timer.invalidate()
    timer2.invalidate()
       //CREATING THE COUNTING TIMER
        let aSelector : Selector = "updateTime"
        timer2 = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: aSelector, userInfo: nil, repeats: true)
        startTime = NSDate.timeIntervalSinceReferenceDate()
    displayLink?.invalidate()
    displayLink1?.invalidate()
   //MOVING BUTTON 1
    moving1outlet.frame = CGRectMake(120, 400, 100, 100)
      displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
    displayLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
   //DELAY TO MOVE BUTTON 2
     timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timerAction", userInfo: nil, repeats: false)  
}
func timerAction() {
    //MOVING BUTTON 2
    self.moving2outlet.frame = CGRectMake(120, 400, 100, 100)
    self.displayLink1 = CADisplayLink(target: self, selector: "handleDisplayLink1:")
    self.displayLink1?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
}
func updateTime() {
    //COUNTING TIMER
    let currentTime = NSDate.timeIntervalSinceReferenceDate()
    //Find the difference between current time and start time.
    var elapsedTime: NSTimeInterval = currentTime - startTime
    //calculate the minutes in elapsed time.
    let minutes = UInt8(elapsedTime / 60.0)
    elapsedTime -= (NSTimeInterval(minutes) * 60)
    //calculate the seconds in elapsed time.
    let seconds = UInt8(elapsedTime)
    elapsedTime -= NSTimeInterval(seconds)
    //find out the fraction of milliseconds to be displayed.
    let fraction = UInt8(elapsedTime * 100)
    //add the leading zero for minutes, seconds and millseconds and store them as string constants
    let strMinutes = String(format: "%02d", minutes)
    let strSeconds = String(format: "%02d", seconds)
    let strFraction = String(format: "%02d", fraction)
    //concatenate minuets, seconds and milliseconds as assign it to the UILabel
    timerlabel.text = "\(strMinutes):\(strSeconds).\(strFraction)"
}
func handleDisplayLink(displayLink: CADisplayLink) {
    //POSITIONING BUTTON 1
    var buttonFrame = moving1outlet.frame
    buttonFrame.origin.y += -2
    moving1outlet.frame = buttonFrame
    if moving1outlet.frame.origin.y <= 50 {
        displayLink.invalidate()
        displayLink1?.invalidate()
    }
}
func handleDisplayLink1(displayLink1: CADisplayLink) {
    //POSITIONING BUTTON 2
    var button4Frame = moving2outlet.frame
    button4Frame.origin.y += -2
    moving2outlet.frame = button4Frame
    if moving2outlet.frame.origin.y <= 50 {
        displayLink1.invalidate()
    }
}

Thank you. Anton

Anton O.
  • 653
  • 7
  • 27

1 Answers1

0

As I mentioned under point #1 in my answer to your other question, you are modifying frame values, but you undoubtedly are using autolayout, and thus anything that triggers the autolayout engine to layout the subviews will move the views back to where they were defined by the constraints. So the problem isn't the timers, but rather the changing of frame values rather than changing constraints.

One solution is to not change frame values, but rather to create @IBOutlet references for the constraints and then in the display link handlers, change the constant of the constraint rather than changing the frame.

For example, if I wanted to move a view a distance of 50 points using a display link over 2 second period, I would do something like the following.

First, add an outlet for the leading constraint:

enter image description here

Then I would write a display link handler like so:

@IBOutlet weak var leftConstraint: NSLayoutConstraint!

var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime?
let duration = 2.0

override func viewDidLoad() {
    super.viewDidLoad()

    startDisplayLink()
}

func startDisplayLink() {
    startTime = CFAbsoluteTimeGetCurrent()
    displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
    displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}

func stopDisplayLink() {
    displayLink?.invalidate()
    displayLink = nil
}

func handleDisplayLink(displayLink: CADisplayLink) {
    let elapsed = CFAbsoluteTimeGetCurrent() - startTime!
    var percentComplete = CGFloat(elapsed / duration)

    if percentComplete >= 1.0 {
        percentComplete = 1.0
        stopDisplayLink()
    }

    leftConstraint.constant = 100.0 + 50.0 * percentComplete
}

Note, like I suggested before, I don't move a fixed distance per call (because different devices might result in different speeds, the speed might not be constant, etc.), but rather I calculate the "percent complete" based upon time elapsed, and then calculate the coordinate from that.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Ok, but why would the frame be affecting this? – Anton O. Dec 18 '15 at 03:41
  • @AntonO. - You are updating `timerlabel.text`, which triggers the autolayout engine to recalculate frames from the constraints. Thus the constraints override the `frame` of the view you manually moved. That's why you should animate changes in the constraints. (Alternatively, you can also disable constraints by changing the `active` property of the constraint and then you can adjust `frame` values in your display link. But then you lose the benefit of constraints.) – Rob Dec 18 '15 at 04:06