3

So I got this code for my simple iOS app. When I press the touchPressed button, the button should get a new random location on the screen and the labelScore should update itself with the number of button touches. A friend of mine tried this in Objective-C and it works.

The problem is: I have the newScore() in touchedPressed and the button doesn't get a new location. If I comment newScore(), the random location action works.

Thanks in advance.

//  ViewController.swift


import UIKit
import SpriteKit

class ViewController: UIViewController {

@IBOutlet var backgroundImage: UIImageView!

@IBOutlet var scoreLabel: UILabel!



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.
}





// Initial score
var score:Int  = 0



// Update the actual score with the one matching th number of touches
func newScore() { 
    score = score + 1
    scoreLabel.text = "SCORE: \(score)"
}



// Get the random height for the button (with limits)
func randomButtonHeight(min: Float, max:Float) -> Float {
    return min + Float(arc4random_uniform(UInt32((max-90) - min + 1)))



}

@IBAction func touchPressed(button: UIButton) {



    // Find the button's width and height
    var buttonWidth = button.frame.width
    var buttonHeight = button.frame.height

    // Find the width and height of the enclosing view
    var viewWidth = button.superview!.bounds.width
    var viewHeight = button.superview!.bounds.height


    // Compute width and height of the area to contain the button's center
    var xwidth = viewWidth - buttonWidth
    var yheight = viewHeight - buttonHeight

    // Generate a random x and y offset
    var xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
    var yoffset = CGFloat(self.randomButtonHeight(100, max: Float(viewHeight)))

    // Offset the button's center by the random offsets.
    button.center.x = xoffset + buttonWidth / 2
    button.center.y = yoffset + buttonHeight / 2


    newScore() // this works but the action above this doesn't and if I comment this line, the action above works.

}



}
perte
  • 271
  • 2
  • 3
  • 16

2 Answers2

3

Now that @matt has identified the problem, I have another suggestion about how to handle it:

1) Add two new vars to your ViewController:

var newButtonX: CGFloat?
var newButtonY: CGFloat?

2) Set these in touchPressed() when updating the button's location.

// Offset the button's center by the random offsets.
newButtonX = xoffset + buttonWidth / 2
newButtonY = yoffset + buttonHeight / 2
button.center.x = newButtonX!
button.center.y = newButtonY!

3) Add an IBOutlet for the button to your ViewController:

@IBOutlet weak var button: UIButton!

and wire it up in Interface Builder.

4) Then override viewDidLayoutSubviews like so:

override func viewDidLayoutSubviews() {
    if let buttonX = newButtonX {
        button.center.x = buttonX
    }
    if let buttonY = newButtonY {
        button.center.y = buttonY
    }
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • fatal error: unexpectedly found nil while unwrapping an Optional value (lldb) on - button.center.x = buttonX – perte Sep 27 '14 at 18:40
  • make sure you wire up your button to the outlet in the Storyboard – vacawama Sep 27 '14 at 18:43
  • Should I create another? My button is already wired to touchPressed – perte Sep 27 '14 at 18:50
  • Yes. You need to add an `IBOutlet`. `touchPressed` is an `IBAction` connection. You do it the same way, drag from the button to just under your `class ViewController` but this time select `Outlet` from the popup. – vacawama Sep 27 '14 at 18:52
  • Now that worked better than the solution provided by @matt. Many thanks! – perte Sep 27 '14 at 18:55
  • I updated the answer by removing the setting of `newButtonX` and `newButtonY` to nil in `viewDidLayoutSubviews` because if anything else causes the layout to happen, the button snaps back to its original location. In other words, we always need to put the button back when `viewDidLayoutSubviews` runs. @matt's solution would have suffered from this problem as well. – vacawama Sep 27 '14 at 19:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62045/discussion-between-perte-and-vacawama). – perte Sep 27 '14 at 19:11
2

It's because of the way the drawing/layout system works. You have a small timing problem, that's all. What's really happening is that the button is moving, but layout is taking place immediately afterwards and putting it back again. One solution is to wrap everything except the call to newScore in a short delay, like this:

@IBAction func touchPressed(button: UIButton) {
    delay(0) {
        // Find the button's width and height
        var buttonWidth = button.frame.width
        // ...
    }
    self.newScore()
}

Alternatively, turn off Auto Layout in this storyboard / xib file. That solves it too (though this may be unacceptable for other reasons).

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • For the `delay` function see my answer here: http://stackoverflow.com/a/24318861/341994 – matt Sep 27 '14 at 17:47
  • Using the delay partially work because there are times when the button is still there but turning off the Auto Layout works better. – perte Sep 27 '14 at 17:56
  • Yes, I noticed that too. It might help to make the label much bigger (wider) than it needs to be. – matt Sep 27 '14 at 18:15