0

I wanted to have a celebration for a certain event in my code. So I took a swift confetti overlay from GitHub (https://github.com/sudeepag/SAConfettiView) and am trying to use it in my project. Basically, if a certain event happens, it starts an NSTimer. I also defined a showAlert and dismissAlert for popping up a buttonless message on a UIAlertView to say Congrats!. When the timer ticks confettitime (which starts at 0) for the first time, it begins the confetti overlay (successfully). Then, at the third tick, it activates showAlert. The message pops up. It dismisses successfully with dismissAlert at the seventh tick of the timer. But I can not get the overlay to hide/the confetti to disappear. As per the instructions in that GitHub project, I call confettiView.stopConfetti() to make it stop! But it just keeps on displaying that confetti, freezing my UI. I have tried removing the notifications (no effect), significantly altering the confetti code (no effect), and I just can't find a way to hide the overlay confetti view.

I followed the Manual Installation directions in the GitHub project, adding the image assets and SAConfettiView.swift to my Swift XCode project. I had to change a couple of the code bits to start because they crashed (like this comment says: https://github.com/sudeepag/SAConfettiView/pull/6), so maybe that is affecting it?

Here's the code of the SAConfettiView, which is the product of that GitHub project:

import UIKit
import QuartzCore

public class SAConfettiView: UIView {

public enum ConfettiType {
    case Confetti
    case Triangle
    case Star
    case Diamond
    case Image(UIImage)
}

var emitter: CAEmitterLayer!
public var colors: [UIColor]!
public var intensity: Float!
public var type: ConfettiType!
private var active :Bool!

required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setup()
}

public override init(frame: CGRect) {
    super.init(frame: frame)
    setup()
}

func setup() {
    colors = [UIColor(red:0.95, green:0.40, blue:0.27, alpha:1.0),
        UIColor(red:1.00, green:0.78, blue:0.36, alpha:1.0),
        UIColor(red:0.48, green:0.78, blue:0.64, alpha:1.0),
        UIColor(red:0.30, green:0.76, blue:0.85, alpha:1.0),
        UIColor(red:0.58, green:0.39, blue:0.55, alpha:1.0)]
    intensity = 0.5
    type = .Confetti
    active = false
}

public func startConfetti() {
    emitter = CAEmitterLayer()

    emitter.emitterPosition = CGPoint(x: frame.size.width / 2.0, y: 0)
    emitter.emitterShape = kCAEmitterLayerLine
    emitter.emitterSize = CGSize(width: frame.size.width, height: 1)

    var cells = [CAEmitterCell]()
    for color in colors {
        cells.append(confettiWithColor(color))
    }

    emitter.emitterCells = cells
    layer.addSublayer(emitter)
    active = true
}

public func stopConfetti() {
    emitter?.birthRate = 0
    active = false
}

func imageForType(type: ConfettiType) -> UIImage? {
    print(type)

    var fileName: String!

    switch type {
    case .Confetti:
        fileName = "Confetti"
    case .Triangle:
        fileName = "triangle"
    case .Star:
        fileName = "star"
    case .Diamond:
        fileName = "diamond"
    case let .Image(customImage):
        return customImage
    }

    print(type)


    return UIImage(imageLiteral: "\(type)")
}

func confettiWithColor(color: UIColor) -> CAEmitterCell {
    let confetti = CAEmitterCell()
    confetti.birthRate = 6.0 * intensity
    confetti.lifetime = 14.0 * intensity
    confetti.lifetimeRange = 0
    confetti.color = color.CGColor
    confetti.velocity = CGFloat(350.0 * intensity)
    confetti.velocityRange = CGFloat(80.0 * intensity)
    confetti.emissionLongitude = CGFloat(M_PI)
    confetti.emissionRange = CGFloat(M_PI_4)
    confetti.spin = CGFloat(3.5 * intensity)
    confetti.spinRange = CGFloat(4.0 * intensity)
    confetti.scaleRange = CGFloat(intensity)
    confetti.scaleSpeed = CGFloat(-0.1 * intensity)
    confetti.contents = imageForType(type)!.CGImage
    return confetti
}

public func isActive() -> Bool {
    return self.active
}
}

And here is the relevant code in my project where I call the confetti:

var confettitimer = NSTimer()
var confettitime = 0
var alreadyconfettied = false

func confetti() {
confettitime++ 
let confettiView = SAConfettiView(frame: self.view.bounds)
self.view.addSubview(confettiView)
if confettitime == 1 {
   confettiView.type = .Confetti
   confettiView.startConfetti()
   alreadyconfettied = true
 }
if confettitime == 3 {
   showAlert2()
 }
if confettitime == 7 {
   dismissAlert2()
}
if confettitime == 10 {
   confettiView.stopConfetti()
   lp.confettitimer.invalidate()
   lp.confettitime = 0
}
}

if event == true && alreadyconfettied == false {
    confettitimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "confetti", userInfo: nil, repeats: true)
    alreadyconfettied = true
 }

So sorry for all the code! It would be so helpful if you could tell me how I can get this confetti overlay to stop!


UPDATE 1 (still not solved):

So I needed to get the ConfettiView creation to happen inside the same function as the .startConfetti() and .stopConfetti()! There is no way to do this with a timer, because the timer can not call a function when both are inside the same function (see here: https://stackoverflow.com/a/25839597/5700898). Right now, when a certain event happens, it calls the confetti() function...

    func confetti() { // called once and only once when the event is triggered.
        let confettiView = SAConfettiView(frame: self.view.bounds)
        self.view.addSubview(confettiView)
    var confettitimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "confettisub", userInfo: nil, repeats: true)

   func confettisubfunction() {
            lp.confettitime++
            print(lp.confettitime)
    if confettitime == 1 {
       confettiView.type = .Confetti
       confettiView.startConfetti()
       alreadyconfettied = true
     }
    if confettitime == 3 {
       showAlert2()
     }
    if confettitime == 7 {
       dismissAlert2()
    }
    if confettitime == 10 {
       confettiView.stopConfetti()
       confettitimer.invalidate()
       confettitime = 0
    }
    }
    }

But this throws that catastrophic error as I told of above. Basically, this setup is impossible, because the declaration of the view can not be in the same function as where I call it with .startConfetti(), and it can not be a global variable/declaration: - If I do let confettiView = SAConfettiView(frame: self.view.bounds) self.view.addSubview(confettiView) just before class ViewController: UIViewController {, then it does not know what self is, and gives the error "expressions are not allowed at top level." - If I do that same code to define my Confetti UIView just inside the declaration of the view controller, it says "Expected Declaration."

So how can I fix this and get confetti to rain when a certain event occurs, and how can I stop said confetti with the help of a timer (or really anything that will work??)

Thanks for your help in advance!


UPDATE 2 (Almost solved):

On my global level, I basically do this, and I call initconfetti() once per celebration. This lets me have just one confetti view I call startConfetti() and stopConfetti() on, letting me actually stop the confetti once again!!

var confettiView = SAConfettiView()

func initconfetti() {
    confettiView = SAConfettiView(frame: self.view.bounds)
    self.view.addSubview(confettiView)
}

Now I have one problem: I just can't get back to tapping on the original view (my view controller)! How do I kill the confetti view? Maybe I make the bounds 0,0? I have to work on this (I'd still like some suggestions).

I am calling this series of commands when the confetti's over (I don't really know what they all mean):

 confettiView.removeFromSuperview()
    confettiView = SAConfettiView(frame: CGRect(
            origin: CGPoint(x: 0, y: 0),
            size: CGSize(width: 0, height: 0)))
    view.becomeFirstResponder()
    self.view.becomeFirstResponder()

but my View Controller is still not responder to taps anywhere, nothing is happening! What's to do? Is there a function I can use to make my old view come to the foreground again?

Thanks!

Community
  • 1
  • 1
owlswipe
  • 19,159
  • 9
  • 37
  • 82

2 Answers2

3

Every time you enter your confetti function you create a new SAConfettiView and add it to your controller's view. I didn't follow all the if statements but I think it's safe to say that the view you're calling stopConfetti on is a different view from the one you used for startConfetti.

I suggest creating the view outside of the timer routine and only controlling it via the timer.

Phillip Mills
  • 30,888
  • 4
  • 42
  • 57
  • there's nowhere that I can put it that doesn't cause errors (sorry, thanks anyway). Any other suggestions? – owlswipe Mar 12 '16 at 17:48
  • There's probably an answer to that but, since you don't show what you've tried or what the errors are, it's hard to fix them for you. – Phillip Mills Mar 12 '16 at 18:26
  • I now am showing what I tried and what my errors are (see bottom of my question). I think your answer is correct, I just get errors trying to implement your answer. Thanks for your help so far, and please check out my updated question! – owlswipe Mar 13 '16 at 03:20
  • I finally made the confetti go away (will post more soon) but how do I get touching the original View Controller back? Is there a way to kill that view? – owlswipe Mar 13 '16 at 04:05
  • please see update 2 of my question, how do I let the user use the original (non-confetti) view after the confetti rains down, my app no longer responds to touches (how can I make the original view the first responder again?)! – owlswipe Mar 13 '16 at 14:46
  • After `stopConfetti()`, `confettiView.removeFromSuperview()` should be all you need. Creating another view and playing with the responders doesn't make sense to me. – Phillip Mills Mar 13 '16 at 16:27
  • nothing happens, even when I do that! I still can't do anything with the view! – owlswipe Mar 13 '16 at 18:13
  • With the various updates I'm not sure what your code looks like now but, even in the "update 2" section alone, you're creating three instances of `SAConfettiView`. That can't be right. – Phillip Mills Mar 13 '16 at 19:39
  • Okay, so in the end, I called my main function twice (oops), which triggered the "event" twice that called confetti. You helped me so much, thank you!! I accepted your answer because you were pretty much right :). Thanks! – owlswipe Mar 13 '16 at 23:38
0

Check out the comments below the other answer for just about everything, and see below for how to create a subview only once:

First, be sure to only call this once! Do this on the global scope, right underneath the whole class ViewController : UIViewController { thing.

var confettiView = SAConfettiView()

func initconfetti() {
confettiView = SAConfettiView(frame: self.view.bounds)
self.view.addSubview(confettiView)
}
func deinitconfetti() {
confettiView.removeFromSuperview()
}

That's all. Now call those with the help of an NSTimer.

owlswipe
  • 19,159
  • 9
  • 37
  • 82