Fr4nc3sc0NL
Fr4nc3sc0NL

Reputation: 585

update view in Swift

I'm making an app where I'm programmatically moving an image around the screen by reading a CGPoint from an array and setting the center of the image accordingly. In the most simple form it's like this:

for point in array {
     image.center = point
     sleep(1)
}

It happens after pressing a button so the for-loop is inside the @IBAction func ... of the button.

The problem is that the screen isn't updating after an image.center change, but only after the whole for-loop is done, so when the image is at the last point in the array. How do I force the view to update? Or what is the best way to deal with this in general?

Upvotes: 0

Views: 1630

Answers (3)

rdelmar
rdelmar

Reputation: 104082

One more way to do this is to use animateWithDuration:delay:options:animations:completion: and use the delay parameter to give you what delay you want between moves. The following code starts the first move immediately, then each subsequent one after one second.

Swift 2

class ViewController: UIViewController {

    var block = UIView()
    var pointArray = [CGPoint(x: 30, y: 30), CGPoint(x: 100, y: 30), CGPoint(x: 50, y: 300), CGPoint(x: 300, y: 170)]
    var counter = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        block.frame = CGRect(origin: CGPointZero, size: CGSize(width: 50, height: 50))
        block.center = pointArray[0]
        block.backgroundColor = UIColor.redColor()
        view.addSubview(block)
        moveView()
    }

    func moveView() {
        var delay = counter == 1 ? Double(0) : Double(1)
        UIView.animateWithDuration(0.3, delay: delay, options: .CurveLinear, animations: { () -> Void in
            self.block.center = self.pointArray[self.counter]
        }) { (finished) -> Void in
            self.counter++
            if self.counter < self.pointArray.count {
                self.moveView()
            }
        }
    }
}

Swift 3, 4, 5

class ViewController: UIViewController {

    var block = UIView()
    var pointArray = [CGPoint(x: 30, y: 30), CGPoint(x: 100, y: 30), CGPoint(x: 50, y: 300), CGPoint(x: 300, y: 170)]
    var counter = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        block.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 50, height: 50))
        block.center = pointArray[0]
        block.backgroundColor = UIColor.red
        view.addSubview(block)
        moveView()
    }

    func moveView() {
        let delay = counter == 1 ? Double(0) : Double(1)
        UIView.animate(withDuration: 0.3, delay: delay, options: .curveLinear, animations: { () -> Void in
            self.block.center = self.pointArray[self.counter]
        }) { (finished) -> Void in
            self.counter+=1
            if self.counter < self.pointArray.count {
                self.moveView()
            }
        }
    }
}

Upvotes: 0

Greg
Greg

Reputation: 25459

I would use keyframe animation. Lets assume this is your points:

let val: [CGPoint] = [CGPoint(x:10, y:20), CGPoint(x:100, y:80), CGPoint(x:200, y:80), CGPoint(x:200, y:400), CGPoint(x:30, y:400), CGPoint(x:30, y:100)]

After you press button you call animation:

       let duration = NSTimeInterval(2)

        UIView.animateKeyframesWithDuration(duration, delay: 0.0, options: nil, animations: {

            self.myView.center = CGPoint(x: 10, y: 20)

            for (index, point) in enumerate(self.val) {
                UIView.addKeyframeWithRelativeStartTime(Double(index) *  1.0 / Double(self.val.count), relativeDuration: 1.0 / Double(self.val.count), animations: {
                    self.myView.center = point
                })
            }
        }, completion: nil)

Of course you can customise your animation duration, delay and so on. Just remember in

UIView.addKeyframeWithRelativeStartTime relativeDuration...

there are relative values so for example duration should be between 0-1 where 1 is your total duration you specified in

UIView.animateKeyframesWithDuration...

Upvotes: 1

mastro35
mastro35

Reputation: 143

Remove the sleep as @matt advised and try to call the method layoutifneeded on the parent view of your image instead of the sleep. The view needs to be redrawn, so I think this could make the trick...

Upvotes: 0

Related Questions