Search code examples
iosswiftuilabeluiviewanimation

How to create multiple labels without them all disappearing at once?


I used the code below to create a label every time a button is pressed, then have the label move to a specific location, and then have it delete.

My problem is it can't create multiple labels because it's deleting the label way too soon, because it removes everything named label.

How can I fix this so it creates multiple labels that only get deleted when a label individually completes its animation?

A solution I've thought of, but can't figure out is where you have it create a label with a different name such as label1, label2, and so on, so that way it can delete a specific label when it completes it's animation, instead of deleting all of them.

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

func createLabel() {

    // Find the button's width and height
    let labelWidth = label.frame.width

    // Find the width and height of the enclosing view
    let viewWidth = self.view.frame.width

    // Compute width and height of the area to contain the button's center
    let xwidth = viewWidth - labelWidth

    // Generate a random x and y offset
    let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))

    // Offset the button's center by the random offsets.
    label.center.x = xoffset + labelWidth / 2
    label.center.y = 300

    label.font = UIFont(name:"Riffic Free", size: 18.0)
    label.textColor = UIColor.white
    label.textAlignment = .center
    label.text = "+1"
    self.view.addSubview(label)
}

func clearLabel() {
    UIView.animate(withDuration: 0.9, delay: 0.4, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: {

        self.label.center = CGPoint(x: 265, y: 75 )

    }, completion: { (finished: Bool) in
        self.label.removeFromSuperview()
    })
}

@IBAction func clicked(_ sender: Any) {
    createLabel()
    clearLabel()
}

Solution

  • The problem is with this code:

    @IBAction func clicked(_ sender: Any) {
        createLabel()
        clearLabel()
    }
    

    There are two issues. The first is that you are doing the animation and removal before you even give the label a chance to become part of the interface. You need to introduce a delay (you can use my delay utility, https://stackoverflow.com/a/24318861/341994):

    @IBAction func clicked(_ sender: Any) {
        createLabel()
        delay(0.1) {
            clearLabel()
        }
    }
    

    The second problem, as you have rightly said, is that you have one label instance variable, which you are using to share the label between createLabel and clearLabel. Thus another label cannot come along during the animation.

    But you don't actually need any instance variable. So get rid of your label declaration completely! Instead, modify createLabel so that it actually creates the label (i.e. calls UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))) as a local variable, and then returns a reference to label it creates, like this:

    func createLabel() -> UILabel {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        // ...
        return label
    }
    

    ... And then just have clearLabel take that same label as a parameter so that it moves that label and removes it at the end of the animation, like this:

    func clearLabel(_ label : UILabel) {
        // ...
    }
    

    Your clicked implementation will thus look like this, passing the label out of createLabel and into clearLabel:

    @IBAction func clicked(_ sender: Any) {
        let label = self.createLabel()
        delay(0.1) {
            self.clearLabel(label)
        }
    }
    

    (The remaining details of modifying createLabel and clearLabel to make that work are left as an exercise for the reader.)

    Now each tap on the button creates and animates and removes a new label, independently of whatever else may have happened before.

    enter image description here