I'm trying to add a navigation bar refresh button to update my table view content that coming from JSON and beside that i want my table view keep updating automatically every 5 minutes if the user hasn't pressed the refresh button and changing the button to be an Activity indicator to let the user that is been updating when the table view updating the data
my refresh button IBAction code :
#IBAction func refreshButton(_ sender: Any) {
self.getCoinData()
self.coinTableView.reloadData()
}
Swift 4+
Create Timer & on completion of webservice show the reload icon
class ViewController: UIViewController {
var timer: Timer!
var refreshBarButtonActivityIndicator: UIBarButtonItem!
var refreshBarButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
barItemSetUp()
}
func barItemSetUp() {
///SettingReloadButton
let image = UIImage(named: "refresh-25")?.withRenderingMode(.alwaysOriginal)
refreshBarButton = UIBarButtonItem(image: image, style: .plain, target: self, action: nil)
///Setting activity Indicator
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView.init(activityIndicatorStyle: .gray)
refreshBarButtonActivityIndicator = UIBarButtonItem(customView: activityIndicator)
activityIndicator.startAnimating()
toogleIndicator()
}
deinit {
timer?.invalidate()
}
func toogleIndicator() {
// 5 minutes = 300 seconds //write here 300
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refreshNow), userInfo: nil, repeats: true)
}
#objc func refreshNow() {
//here i am creating delay for sample purpose
//here write your webservice code on completion of webservice change bar button item
self.navigationItem.rightBarButtonItem = refreshBarButtonActivityIndicator
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.navigationItem.rightBarButtonItem = self.refreshBarButton
}
}
}
Output:-
First add the timer which is refresh data every 5 minutes. At timer, you add the web service.
var timer = Timer()
func runTimer(){
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
}
func updateTimer() {
self.getCoinData()
self.coinTableView.reloadData()
}
override func viewWillAppear(_ animated: Bool){
runTimer()
}
To reload your UITableView after every 5 minutes, you need to schedule a Timer that executes a block of code every 5 minutes, i.e.
var timer: Timer?
override func viewDidLoad()
{
super.viewDidLoad()
self.timer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true, block: {[weak self] (tt) in
self?.getCoinData()
self?.coinTableView.reloadData()
})
timer?.fire()
}
deinit
{
timer?.invalidate()
}
You can change the time interval according to your requirement. You need to give the time interval in seconds.
Related
The timer starts and countdowns while updating the title of the button when the view is loaded. I've called startTimer in viewDidLoad. This works fine as expected.
However, the problem I faced is when I call startTimer from the IBAction. I've verified the timer is working and the selector is being called. The button title doesn't change tho. What could be wrong in this case?
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton?
var timer: Timer?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startTimer()
}
func startTimer() {
countdownSeconds = 60
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
countdownSeconds -= 1
let countdown = countdownFormatter.string(from: TimeInterval(countdownSeconds))!
if countdownSeconds > 0 {
UIView.performWithoutAnimation {
button?.setTitle(countdown, for: .normal)
}
} else {
timer?.invalidate()
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
startTimer()
}
}
I'm new to Swift and I'm trying to make timer(in the label) that starts with a long press on the button. At the same time I want to change the long press button image when press the long press button. I leave button, I want the button revert back.
What might be wrong?
#IBOutlet weak var myBtn: UIButton!
func initGesture()
{
{ let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:)))
myBtn.addGestureRecognizer(longGesture)
}
}
func TimerAction()
{
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(longTap), userInfo: nil, repeats: false)
myBtn.setImage(UIImage(named: "xxx.png"), for: .normal)
}
#IBOutlet weak var lbl: UILabel!
func start()
{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController2.updateTime as (ViewController2) -> () -> ())), userInfo: nil, repeats: true)
}
func updateTimer () {
count += 1
let hours = Int(count) / 3600
let minutes = Int(count) / 60 % 60
let seconds = Int(count) % 60
label.text = String(format: "%02i:%02i:%02i",hours,minutes,seconds)
}
func reset()
{
timer.invalidate()
count = 0
label.text = "00:00:00"
}
You can get TouchUpInside and TouchUpOutside also TouchDown action of button don't use the gesture.
class ViewController: UIViewController {
#IBOutlet weak var lbl: UILabel!
#IBOutlet weak var myBtn: UIButton!
var timer: NSTimer!
override func viewDidLoad() {
super.viewDidLoad()
myBtn.addTarget(self, action: "buttonDown:", forControlEvents: .TouchDown)
myBtn.addTarget(self, action: "buttonUp:", forControlEvents: [.TouchUpInside, .TouchUpOutside])
}
func buttonDown(sender: AnyObject) {
singleFire()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "rapidFire", userInfo: nil, repeats: true)
}
func buttonUp(sender: AnyObject) {
timer.invalidate()
count = 0
label.text = "00:00:00"
}
func singleFire() {
myBtn.setImage(UIImage(named: "xxx.png"), for: .normal)
}
func rapidFire() {
count += 1
let hours = Int(count) / 3600
let minutes = Int(count) / 60 % 60
let seconds = Int(count) % 60
label.text = String(format: "%02i:%02i:%02i",hours,minutes,seconds)
}
}
You should implement button events instead of using the gesture recognisers here. The button events i talk about are TouchDown and TouchUpInside. TouchDown will tell you when a button is being pressed and touchupinside will tell you when the user lifted their finger from button.
So you will change the button image and start your timer on touchdown event. Then you will revert back on touchupinside event.
https://developer.apple.com/documentation/uikit/uicontrolevents
Try this,
set first the image of the button depending on its state.
self.arrowIcon.setImage(UIImage(named: "normalImage.png"), for: .normal)
self.arrowIcon.setImage(UIImage(named: "pressedImage.png"), for: .highlighted)
I'm currently learning Swift and iOS programming. I want to make a timer that starts with a long press on the button. At the same time I want to change the UIButton image. I leave button, I want the button revert back. Is this possible?
You absolutely can do this. You have to add LongPressGestureRecognizer:
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(yourAction))
button.addGestureRecognizer(longPress)
And then in your func start the timer and change the button's image:
func yourAction(){
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: false)
button.setImage(UIImage(named: "yourImage"), for: .normal)
}
func timerAction(){
//Do whatever you want
}
You need to implement your own solution for this, something along the lines
1- User touches down (UIControlEvent touchDown), you start a time that fires every x secs/millisecs
2- Timer will fire the action over and over
3- User touches up UICOntrolEvent touchUp, you cancel your timer
So you bind those 2 events to different functions, and start/kill your timers with appropriate actions
How that helps
I will add a code for demonstration:
#IBOutlet var button: UIButton!
var timer: Timer!
var speedAmmo = 100
#IBAction func buttonDown(sender: AnyObject) {
singleFire()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:#selector(rapidFire), userInfo: nil, repeats: true)
}
#IBAction func buttonUp(sender: AnyObject) {
timer.invalidate()
}
func singleFire() {
if speedAmmo > 0 {
speedAmmo -= 1
print("bang!")
} else {
print("out of speed ammo, dude!")
timer.invalidate()
}
}
func rapidFire() {
if speedAmmo > 0 {
speedAmmo -= 1
print("bang!")
} else {
print("out of speed ammo, dude!")
timer.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action:#selector(buttonDown(sender:)), for: .touchDown)
button.addTarget(self, action:#selector(buttonUp(sender:)), for: [.touchUpInside, .touchUpOutside])
}
Please try and let me know!
looking for some help for this, I have a timer that works after submitting a password which is great, but I then need to disable the button after the timer starts and is disabled for a period of time, (in the code I have entered a nominal 90 seconds)
however the button is not disabling.
if anybody could show me where I am going wrong that would be awesome.
import UIKit
class appiHour: UIViewController {
var timer = Timer()
var counter = 60
var password_Text: UITextField?
func enableButton() {
self.timerStartButton.isEnabled = true
}
#IBOutlet weak var timerLabel: 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.
}
#IBAction func timerStartButton(_ sender: Any) {
var password_Text: UITextField?
let alertController = UIAlertController(title: "To start your own 2 Cocktails for £10 APPi Hour", message: "get a memeber of the team to enter the password, but use it wisely, as you can only use it once per day, with remember great power comes great responsability", preferredStyle: UIAlertControllerStyle.alert)
let tickoff_action = UIAlertAction(title: "let the APPiness commence", style: UIAlertActionStyle.default) {
action -> Void in
self.timerStartButton.isEnabled = false
Timer.scheduledTimer(timeInterval: 90, target: self, selector: #selector(appiHour.enableButton), userInfo: nil, repeats: false)
if let password = password_Text?.text{
print("password = \(password)")
if password == "baruba151" {
self.counter = 60
self.timerLabel.text = String(self.counter)
self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(appiHour.updateCounter), userInfo: nil, repeats: true)
}
} else {
print("No password entered")
}
}
alertController.addTextField { (txtpassword) -> Void in
password_Text = txtpassword
password_Text!.isSecureTextEntry = true
password_Text!.placeholder = ""
}
alertController.addAction(tickoff_action)
self.present(alertController, animated: true, completion: nil)
}
#IBOutlet weak var timerStartButton: UIButton!
func updateCounter() {
counter -= 1
timerLabel.text = String(counter)
if counter == 0{
timer.invalidate()
counter = 0
}
}
}
As a secondary question is it possible to run the timer while the app is in the background? i know apple frowns on this aside for Sat Nav, Music apps etc. But is there a method in which the timer is held and a notification is sent locally letting the user know the timer has ended?
thanks in advance.
I suspect that your action may not be hooked up to your button. I just tried the following code with no issues. The button gets disabled, and then enabled 5 seconds later:
class ViewController: UIViewController {
#IBOutlet weak var myButton: UIButton!
#IBAction func ButtonPressed(_ sender: Any) {
myButton.isEnabled = false
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(myTimerTick), userInfo: nil, repeats: false)
}
func myTimerTick() {
myButton.isEnabled = true
}
}
So make sure your outlets and actions are hooked up to the button correctly. If you right click on your button, you should see the dots filled in next to the outlet and action. You should see similarly filled in dots in your code.
You can further verify it is hooked up by placing a breakpoint in your "timerStartButton" method and making sure that breakpoint is hit.
Edit to further clarify: You need to connect your code to your Interface build objects. See this article from Apple for a complete tutorial on how to do that.
I'm not 100% sure if this is what you mean. But this would at least satisfy the first part of your request: disable a button whilst a timer is running, and re-enable it once the timer stops.
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var timerCount: UILabel!
#IBAction func buttonPressed(_ sender: UIButton) {
var count = 0
sender.isEnabled = false
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] timer in
count += 1
if count == 5 {
count = 0
sender.isEnabled = true
timer.invalidate()
}
self.timerCount.text = "\(count)"
}
}
Here's a couple of screenshots of what you get.
It's enabled when the user starts off, disabled whilst the count is going then reverts back to its original state with counter at 0 and button enabled. Is that what you're going for?
As far as your second question, what do you mean by
the timer is held
Do you want the timer to keep running whilst the app is in the background, then update the user once the timer has elapsed? If so, take a look at this answer which should point you in the right direction: Continue countdown timer when app is running in background/suspended
I've seen this a couple of times before, but it never occured to me what might be wrong.
Firstly, I want to create the effect of scrambling numbers like they do in those hacking scenes in movies. So, I made an NSTimer to make my delays such that every 0.2 seconds, the numbers change. Then, I made another timer to tell my first timer to
invalidate()
after two seconds. My code is as follows:
import UIKit
class MainPage: UIViewController {
#IBOutlet var genericDeviceName: UITextField!
#IBOutlet var hackButton: UIButton!
#IBOutlet var rightNumber: UILabel!
#IBOutlet var leftNumber: UILabel!
#IBOutlet var detectionText: UILabel!
#IBAction func deviceNameEnter(sender: AnyObject) {
detectionText.text = "Device detected: " + genericDeviceName.text!
if genericDeviceName.text == "" {
detectionText.text = "Error"
}
hackButton.alpha = 1
}
#IBAction func hackDevice(sender: AnyObject) {
var tries = 0
var timer = NSTimer()
var timerStop = NSTimer()
timer = NSTimer (timeInterval: 0.2, target: self, selector: "update", userInfo: nil, repeats: true)
timerStop = NSTimer (timeInterval: 2, target: self, selector: "endTimer", userInfo: nil, repeats: true)
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
func update() {leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")}
func endTimer() {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
}
So... what went wrong? The last few times I tried using NSTimers, they didn't work either. Is my concept of an NSTimer wrong? Or is there an error in my code? There was no error message triggered, it was just that the timer did not trigger and the numbers did not change. Not even "it worked!" was printed to the logs. Please help by suggesting some code. Thank you in advance!
UPDATE
I've updated my code. Here it is:
import UIKit
class MainPage: UIViewController {
#IBOutlet var genericDeviceName: UITextField!
#IBOutlet var hackButton: UIButton!
#IBOutlet var rightNumber: UILabel!
#IBOutlet var leftNumber: UILabel!
#IBOutlet var detectionText: UILabel!
#IBAction func deviceNameEnter(sender: AnyObject) {
detectionText.text = "Device detected: " + genericDeviceName.text!
if genericDeviceName.text == "" {detectionText.text = "Error"}
hackButton.alpha = 1
}
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
func update(timer: NSTimer) {leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")}
func endTimer(timerStop: NSTimer) {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()}
#IBAction func hackDevice(sender: AnyObject) {
var timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: "update:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
var timerStop = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "endTimer:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timerStop, forMode: NSRunLoopCommonModes)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Currently, it seems that the function "endTimer" does not work, due to the variable "timer" not being recognised. Please help. Thank you all so much for your time!
A few things: The selector for an NSTimer should end in a colon (e.g. "update:" or "endTimer:" And the function should take a single parameter: An NSTimer.
Second, the function that the timer calls must be a top-level function of the target. Your update method is a local function of your hackDevice, function, which won't work.
Third, you need to use scheduledTimerWithTimeInterval, as in ShahiM's answer:
var timer = NSTimer.scheduledTimerWithTimeInterval(
0.4,
target: self,
selector: "update:",
userInfo: nil,
repeats: true)
That code crashes if the function in your selector is a nested function because it's not visible to the timer.
Finally, it looks like you need to move the variables diceRoll and diceRollSecond out of your hackDevice function and make them instance variables of your class.
You should move your functions out of hackDevice. Nested functions like this are generally not used in Swift.
For example:
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
var timer = NSTimer()
#IBAction func hackDevice(sender: AnyObject) {
var tries = 0
var timer = NSTimer()
var timerStop = NSTimer()
timer = NSTimer (timeInterval: 0.2, target: self, selector: "update", userInfo: nil, repeats: true)
timerStop = NSTimer (timeInterval: 2, target: self, selector: "endTimer", userInfo: nil, repeats: true)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
}
func update() {
leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")
}
func endTimer() {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()
}
Try using this :
var timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
Also move your update and endTimer methods outside the hackDevice method.
Explanation :
From Apple docs :
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
.
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
So in your code, you only create the timer but it does not start running. You have to either call the addTimer(_ timer: NSTimer,forMode mode: String) to start the timer or you can simply use scheduledTimerWithTimeInterval to launch the timer right away.
You don't nest this kind of function, selector will not find them because they will be exposed after the method exit, after the function leave the last } there will be no a update and endTiemr
Your timer should look like this let timer = NSTimer(timeInterval: 0.2, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
and on the other side func update(timer: NSTimer) {
Also try adding the timer to the run loop after initialisation:
let timer = NSTimer(timeInterval: 0.2, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
In your update you redeclared the variable timer, this way you created a local variable which exists just in the method hackDevice: , remove the var before the timer = NSTimer.scheduledTimer...
Edit:
I rather edit this answer, because here i can add insert code snippet with proper newlines and indents:
class MainPage: UIViewController{
// Your IBOutlets
#IBOutlet var ...
var timer= NSTimter()
// Your methods
}