Home > Software engineering >  Show Multiple Counter in label but at same screen (Swift, IOS)
Show Multiple Counter in label but at same screen (Swift, IOS)

Time:09-13

I am creating an application where I have to show spot on home screen but the user has to unlock the spot to watch it and it has label which starts counter for 60secs . This is my timer function.

timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)

 @objc func updateCounter() {
 if counter > 0 {

            let time = Int(counter/60)
            let dec = Int(counter.truncatingRemainder(dividingBy: 60))
            spotTiming = String(time)   ":"   String(dec)
            }
            counter -= 1
}

And this is how it will be shown on screenenter image description here

After taping on any of the spot the counter will start. But my issue is how to start counter on more than 2 labels with same Counter Function.As user can tap on 2 different spots with the difference of 10secs and I have only one counter function so how I manage to start 2 different counters. Is there any other approach anyone can suggest so I will be very grateful! Thank you. PS Spots can be multiple depending on users.

CodePudding user response:

Assuming you want each "spot" to start a "countdown from 60" when it is tapped, here's a simple example...

We'll create a UIView subclass with a label and round border.

It will also have a "startTime" property:

private var startTime: Date?

when we Tap the view, we'll set the start time to "now":

startTime = Date()

and we'll have a function to update the countdown:

public func timerFired(_ now: Date) {
    if let st = startTime {
        let elapsedSeconds = Int(floor(now.timeIntervalSinceReferenceDate - st.timeIntervalSinceReferenceDate))
        label.text = "\(60 - elapsedSeconds)"
        // if we've reached 60-seconds, set startTime to nil
        //  so we don't keep going to -1, -2, -3 etc
        if elapsedSeconds == 60 {
            self.startTime = nil
        }
    }
}

In our controller, we will have a single repeating timer that fires every 0.3 seconds. If we fire it only once-per-second, we could "skip" numbers:

    // use less-than 1-second for the timeInterval... otherwise, we *could* end up with
    //  skipped seconds. That is, we might see "60  59  58  56  55  53 etc"
    Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: true)

Each time the timer func fires, we'll tell each "spot" view by calling .timerFired(), and the "spot" view will then compare its own startTime with the new time and update its label.

So, here's our custom SpotView class:

class SpotView: UIView {
    
    private var startTime: Date?
    
    private let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        label.numberOfLines = 0
        label.text = "Unlock\nto See"
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: centerXAnchor),
            label.centerYAnchor.constraint(equalTo: centerYAnchor),
        ])
        layer.borderColor = UIColor.red.cgColor
        layer.borderWidth = 1
        
        let g = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
        addGestureRecognizer(g)
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        let m: CGFloat = min(bounds.width, bounds.height)
        layer.cornerRadius = m * 0.5
    }
    @objc func gotTap(_ g: UITapGestureRecognizer) {
        // set the startTime property to "now"
        startTime = Date()
        label.text = "60"
    }
    public func timerFired(_ now: Date) {
        if let st = startTime {
            let elapsedSeconds = Int(floor(now.timeIntervalSinceReferenceDate - st.timeIntervalSinceReferenceDate))
            label.text = "\(60 - elapsedSeconds)"
            // if we've reached 60-seconds, set startTime to nil
            //  so we don't keep going to -1, -2, -3 etc
            if elapsedSeconds == 60 {
                self.startTime = nil
            }
        }
    }
}

and a sample controller:

class FourSpotsVC: UIViewController {
    
    var theSpots: [SpotView] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let w: CGFloat = 100.0
        let h: CGFloat = 100.0
        
        var x: CGFloat = 40.0
        var y: CGFloat = 100.0
        
        // let's add 4 "Spot" views
        for i in 0..<4 {
            x = i % 2 == 0 ? 40.0 : 160.0
            let v = SpotView()
            v.frame = CGRect(x: x, y: y, width: w, height: h)
            view.addSubview(v)
            theSpots.append(v)
            y  = h   20.0
        }
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // use less-than 1-second for the timeInterval... otherwise, we *could* end up with
        //  skipped seconds. That is, we might see "60  59  58  56  55  53 etc"
        Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: true)
    }
    @objc func timerFunc() {
        let n = Date()
        theSpots.forEach { v in
            v.timerFired(n)
        }
    }
}

And it looks like this when running (I tapped the Top Spot, then waited about 10-seconds and tapped the Second Spot):

enter image description here

  • Related