how can i update my progress bar step by step? - ios

i have code where i add one constant every second to my array, how can i update my progress bar every second when array is changed?
var array: [Int] = []
override func viewWillAppear(_ animated: Bool) {
upgradeArray()
}
func upgradeArray() {
for i in 0...10 {
sleep(1)
array.append(i)
print(i)
let percentProgress = Float(Float(self.array.count)*100.0/10.0)
progressBar.setProgress(percentProgress, animated: true)
}
}

Use a timer. Assuming your progress view is 0 to 1 and incrementing by 0.1 each second...
var timer: Timer?
var array = [String]()
func doStuff() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
self.array.append("foo")
let change: Float = 0.1
self.progressView.progress = self.progressView.progress + (change)
if self.progressView.progress >= 1.0 {
self.timer?.invalidate()
}
})
}

done with DispatchQueue.global(priority: .default).async
func upgradeArray() {
for i in 0...10 {
DispatchQueue.global(priority: .default).async {
//sleep(1)
self.array.append(i)
print(i)
DispatchQueue.main.async(execute: {
let percentProgress = Float(Float(self.array.count)*100.0/10.0)
self.progressBar.setProgress(percentProgress, animated: true)
})
}
}
}

Related

Resend OTP timer functionality in Swift

I want to set a timer of 30 seconds to send an OTP to a phone number. And while the timer is running, the resend OTP button should be disabled. Once the timer ends, the resend OTP button should get enabled and the timer label should be hidden. Onclick of the resend OTP button, the same process should continue.
In the code that I have written, the timer label is hidden the entire time and is constantly going into the if loop where it is constantly printing "invalidated".
Below is the code that I have written.
Updated Code:
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var resendOTPBtn: UIButton!
var countdownTimer: Timer!
var totalTime = 30
override func viewDidLoad() {
super.viewDidLoad()
if AFWrapper.isConnectedToInternet() {
timerLabel.text = ""
sendOTPCode()
sendOTPAPICall()
resendOTPBtn.isEnabled = false
} else {
self.textPopupAlert(title: ALERT_TITLE, message: OFFLINE_MSG)
}
}
// in case user closed the controller
deinit {
countdownTimer.invalidate()
}
#objc func updateTimerLabel() {
totalTime -= 1
timerLabel.text = "\(timeFormatted(totalTime))"
if totalTime == 0 {
timerLabel.text = ""
countdownTimer.invalidate()
resendOTPBtn.isEnabled = true
}
}
#IBAction func resendOTPBtnClicked(_ sender: Any) {
if AFWrapper.isConnectedToInternet() {
sendOTPAPICall()
timerLabel.text = ""
totalTime = 31
resendOTPBtn.isEnabled = false
sendOTPCode()
}
else {
self.textPopupAlert(title: ALERT_TITLE, message: OFFLINE_MSG)
}
}
func sendOTPCode() {
self.countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimerLabel), userInfo: nil, repeats: true)
}
Initial Code:
#IBAction func resendOTPBtnClicked(_ sender: Any) {
if AFWrapper.isConnectedToInternet() {
sendOTPAPICall()
countDownTime()
}
else {
self.textPopupAlert(title: ALERT_TITLE, message: OFFLINE_MSG)
}
}
func countDownTime() {
DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
self.timerLabel.isHidden = false
self.resendOTPBtn.isEnabled = false
if self.timerLabel.isHidden == false {
self.startTimer()
} else {
self.countdownTimer.invalidate()
self.resendOTPBtn.isEnabled = true
self.timerLabel.isHidden = true
}
}
}
// Method to start the timer when resend OTP button is clicked.
func startTimer() {
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
DispatchQueue.main.async(){
self.timerLabel.text = self.timeFormatted(self.totalTime)
if self.totalTime != 0 {
self.totalTime -= 1
} else {
print("Invalidated")
self.endTimer()
}
}
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
// let hours: Int = totalSeconds / 3600
return String(format: "%02d:%02d", minutes, seconds)
}
Any solutions would be appreciated. Thank you!
#IBOutlet weak var resendCodeTimerLabel: UILabel!
#IBOutlet weak var resendCodeButton: UIButton!
var resendCodeCounter = 30
var resendCodeTimer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
resendCodeTimerLabel.text = ""
sendOTPCode()
}
// in case user closed the controller
deinit {
resendCodeTimer.invalidate()
}
#objc func updateTimerLabel() {
resendCodeCounter -= 1
resendCodeTimerLabel.text = "Resend code in \(resendCodeCounter) seconds."
if resendCodeCounter == 0 {
resendCodeButton.isEnabled = true
resendCodeTimer.invalidate()
}
}
#IBAction func resendAgainButtonClicked(_ sender: UIButton) {
OTPTextField.text = ""
resendCodeCounter = 31
resendCodeButton.isEnabled = false
sendOTPCode()
}
func sendOTPCode() {
//Whatever your api logic
if otpSent {
self.resendCodeTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimerLabel), userInfo: nil, repeats: true)
}
}

How to change images in UI according to time duration in Swift

I am doing iWatch application. In that, User has to walk for 6 minutes. In that, I am showing timer. According to that, I have to change walking images. I have change 8 images.
like image1.png, image2.png, etc to image8.png
How to change image according to time duration.
I am new to Swift language.
#IBOutlet weak var walkingImage: WKInterfaceImage!
let walkingImagesArray = ["walking1.png", "walking2.png", "walking3.png","walking4.png", "walking5.png","walking6.png", "walking7.png", "walking8.png"]
override func awake(withContext context: Any?) {
super.awake(withContext: context)
self.startTimer()
}
func startTimer() {
self.countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
count = count + 1
if(count < 361) {
timerLabel.setText(String(timeString(time: TimeInterval(count))))
//here I have to use switch case to change image
} else {
print("Workout completed")
countdownTimer.invalidate()
session.end()
builder.endCollection(withEnd: Date()) { (success, error) in
self.builder.finishWorkout { (workout, error) in
DispatchQueue.main.async() {
self.session = nil
self.builder = nil
}
}
}
}
print("\(count)")
}
Any suggestions?
In case of less than 60 seconds resulting image is named "walking0.png"
Solution:
func image(time: Int) -> String {
let sekInt = time / 60
let sek = String(sekInt)
return "walking\(sek).png"
}
self.walkingImage.setImage(UIImage(contentsOfFile: image(time:count)))
Unit Tests:
func testExample() {
XCTAssertEqual(image(time: 360), "walking6.png")
XCTAssertEqual(image(time: 359), "walking5.png")
XCTAssertEqual(image(time: 301), "walking5.png")
XCTAssertEqual(image(time: 300), "walking5.png")
XCTAssertEqual(image(time: 299), "walking4.png")
XCTAssertEqual(image(time: 61), "walking1.png")
XCTAssertEqual(image(time: 60), "walking1.png")
XCTAssertEqual(image(time: 59), "walking0.png")
XCTAssertEqual(image(time: 1), "walking0.png")
XCTAssertEqual(image(time: 0), "walking0.png")
}
You can start timer for 6 minutes and change your image every 6 minutes.
var timer: Timer?
var currentIndex: Int = 0
let walkingImagesArray = ["walking1.png", "walking2.png", "walking3.png","walking4.png", "walking5.png","walking6.png", "walking7.png", "walking8.png"]
func startTimer() {
if let timer = timer {
self.timer = timer
} else {
Timer.scheduledTimer(withTimeInterval: 360, repeats: true) { timer in
//Change your image here
self.currentIndex = self.currentIndex + 1
self. walkingImage.image = self.walkingImagesArray[self.currentIndex]
}
}
}
func stopTimer() {
self.timer?.invalidate()
self.timer = nil
}

When I pause my timer, then try to start it again, it does not run

I'm building an app in Swift 3. When I press start the first time my timer begins, but when I pause it and try to press start again, the timer does not budge. To give context, the timer, with an amount of time attached to it, is selected from a table. each time the timer load, the start button works initially.
protocol TimerViewControllerDelegate: class {
func viewController(_ controller: ViewController, didFinishEditing item: TaskData)
}
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var pauseButton: UIButton!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var timerTaskName: UILabel!
#IBOutlet weak var timerTimeSetting: UILabel!
#IBOutlet weak var progressView: UIProgressView!
weak var delegate: TimerViewControllerDelegate?
var timerTask: TaskData?
var timer: Timer?
var progressViewSpeed: Double = 0.0
#IBAction func cancel(_ sender: Any) {
timer?.invalidate()
dismiss(animated: true, completion: nil)
delegate?.viewController(self, didFinishEditing: timerTask!)
}
#IBAction func startButtonTapped(_ sender: Any) {
timerTask?.startTime = Date()
runTimer()
if timerTask?.isTaskRunning == true {
runTimer()
self.startButton.isEnabled = false
self.pauseButton.isEnabled = true
} else {
//retrieve start time and run
timerTask?.startTime = Date()
runTimer()
self.startButton.isEnabled = false
self.pauseButton.isEnabled = true
}
}
func runTimer() {
guard timer == nil else {
return
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
#IBAction func pauseButtonTapped(_ sender: UIButton) {
if timerTask?.isTaskRunning == true {
timer?.invalidate()
if let timerTask = timerTask, timerTask.isTaskRunning {
// Calculate the difference between now and when the timerTask was started
let difference = Int(Date().timeIntervalSince(timerTask.startTime!))
timerTask.taskRemaining -= difference
if timerTask.taskRemaining == 0 {
// Do something when there's no time remaining on the task?
}
timerTask.startTime = nil
}
}
else {
timerTask?.startTime = Date()
runTimer()
self.pauseButton.setTitle("Pause",for: .normal)
}
self.startButton.isEnabled = true
self.pauseButton.isEnabled = false
}
/*
#IBAction func resetButtonTapped(_ sender: Any) {
timer.invalidate()
seconds = 60
self.timerLabel.text = timeString(time: TimeInterval(seconds))
if self.resumeTapped == true {
self.resumeTapped = false
self.pauseButton.setTitle("Pause",for: .normal)
}
isTimerRunning = false
pauseButton.isEnabled = false
startButton.isEnabled = true
}
*/
func updateTimer() {
guard let timerTask = timerTask else {
return
}
if timerTask.taskRemaining < 1 {
timer?.invalidate()
timer = nil
//Send alert to indicate "time's up!"
} else {
updateTime()
}
progressViewSpeed = 1 / Double(timerTask.taskRemaining)
progressView.progress += Float(progressViewSpeed)
}
func timeString(time:TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let timerTask = timerTask else {
return
}
if timerTask.isTaskRunning {
startButton.isEnabled = false
pauseButton.isEnabled = true
runTimer()
} else {
startButton.isEnabled = true
pauseButton.isEnabled = false
}
timerTaskName.text = timerTask.task
updateTime()
self.progressView.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi / 2).scaledBy(x: 1, y: 150)
}
func updateTime() {
guard let timerTask = timerTask else {
return
}
if let startTime = timerTask.startTime {
// Calculate the difference between now and when the timerTask was started
let difference = Int(Date().timeIntervalSince(startTime))
if timerTask.taskRemaining == difference {
// Do something when there's no time remaining on the task
timer?.invalidate()
timer = nil
}
timerLabel.text = timeString(time: TimeInterval(timerTask.taskRemaining - difference))
} else {
timerLabel.text = timeString(time: TimeInterval(timerTask.taskRemaining))
}
}
}
Once you've invalidated an NSTimer, you can't use it again. You should create the new object.
See here for more From NSTimer Docs
Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes and releases the timer, either just before the invalidate method returns or at some later point. Once invalidated, timer objects cannot be reused.
You need to invalidate it and recreate it. "isPaused" bool to keep track of the state
var isPaused = true
var timer: Timer?
#IBAction func pauseResume(sender: AnyObject) {
if isPaused{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
pauseButton.isHidden = false
startButton.isHidden = true
isPaused = false
} else {
pauseButton.isHidden = true
startButton.isHidden = false
timer.invalidate()
isPaused = true
}
}

Swift call timer from ViewController

I have a TimerManager class that I would like to access in multiple ViewControllers but I can't figure out a good way to do it. My code is as follows:
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
}
else {
endTimer()
}
}
}
In my ViewController I would like to be able to access my update() function to update a timer (which is a UILabel) on my actual page, but since my startTimer() function calls it every second, I don't know how to access update() every time it is called. I briefly looked into protocols but I'm not really sure how they work or if that would be useful in my case.
Any help would be appreciated!
As #sschale suggested, you can do this by using a singleton to ensure that you will be accessing the same instance anywhere in your code. To do this, you need to set the init to private and provide a static member variable to access your single instance.
class TimerManager
{
static let sharedInstance = TimerManager()
private var timer: NSTimer
private var timeRemaining: Int
private init()
{
let initialTime = 1
self.timer = NSTimer()
self.timeRemaining = initialTime
}
private init(initialTime: Int)
{
self.timer = NSTimer()
self.timeRemaining = initialTime
}
...
}
Then in your ViewControllers you can just call it like this:
TimerManager.sharedInstance.startTimer()
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
private var intervalBlock: (TimerManager -> ())?
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer(intervalBlock: (TimerManager -> ())? = nil) {
self.intervalBlock = self
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.intervalBlock = nil
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
intervalBlock()
}
else {
intervalBlock()
endTimer()
}
}
}
Below is one of the best implementations of Timer on the background queue I found from this article
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}
Usage: -
In any of your ViewControllers
For example: -
class MyViewController: UIViewController {
// MARK: - Properties
var timer: RepeatingTimer!
// MARK: - ViewController LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
timer = RepeatingTimer(timeInterval: 1)
timer.eventHandler = {
print("Timer called")
}
}

Create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds

Basically I want to create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds. Could anyone point me in the right direction for using NSTimer in swift with UIProgressView?
If anyone's interested here is a Swift 5 working version :
extension UIProgressView {
#available(iOS 10.0, *)
func setAnimatedProgress(progress: Float = 1, duration: Float = 1, completion: (() -> ())? = nil) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
DispatchQueue.main.async {
let current = self.progress
self.setProgress(current+(1/duration), animated: true)
}
if self.progress >= progress {
timer.invalidate()
if completion != nil {
completion!()
}
}
}
}
}
Usage :
// Will fill the progress bar in 70 seconds
self.progressBar.setAnimatedProgress(duration: 70) {
print("Done!")
}
I created my own solution after searching for one and coming across this question.
extension UIProgressView {
func setAnimatedProgress(progress: Float,
duration: NSTimeInterval = 1,
delay: NSTimeInterval = 0,
completion: () -> ()
) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
sleep(UInt32(delay))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = Float(pow(duration, -1))
self.setProgress(progress, animated: true)
}
sleep(UInt32(duration))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = 1
completion()
}
}
}
}
Declare the properties:
var time = 0.0
var timer: NSTimer
Initialize the timer:
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
Implement the setProgress function:
func setProgress() {
time += 0.1
dispatch_async(dispatch_get_main_queue(), {
progressView.progress = time / 3
})
if time >= 3 {
timer.invalidate()
}
}
(I'm not 100% sure if the dispatch block is necessary, to make sure the UI is updated in the main thread. Feel free to remove this if it isn't necessary.)

Resources