How to give count down for OTP in Swift - ios

I need to give 60 sec time countdown for resend otp. and if i click verifyOtp butn then count down to be stop. and i want that otp to be expaire. in count down time resend otpbutton to be disable.. please suggest me how to set count down for resendotp.
i am getting otp in registrService().
do{
var json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: Any]
let regUserStatus = json["status"] as? String
if regUserStatus == "sucess"
{
print("the json regggggggggggis \(json)")
let phNum = json["mobile_number"] as? Int
let status = json["status"] as? String
self.otpField = json["otp"] as? Int
}
else{
DispatchQueue.main.async {
self.registerButton.isHidden = false
self.sendOtpButton.isHidden = true
self.otpTextField.isHidden = true
self.resendButn.isHidden = true
self.otpcountLabel.isHidden = true
AlertFun.ShowAlert(title: "", message: "user exist", in: self)
}
}
}
resendButton .
#IBAction func resendOtpButn(_ sender: Any) {
print("resendotp tapped")
registerService()
}

After you have started the OTP process you need to create a 60 seconds timer that when it expires it runs a selector method:
create a class level Timer property:
var otpTimer: Timer?
create the timer when your reg button is clicked
#IBAction func regButn(_ sender: Any) {
registerService()
startTimer()
}
func startTimer() {
optTimer?.invalidate(). //cancels it if already running
optTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(timerDidFire(_:)), userInfo: userInfo, repeats: false)
}
When your timer hits zero the selector will be called, and you can do whatever you want to do to expire the otp request
#objc func timerDidFire(_ timer: Timer) {
// timer has completed. Do whatever you want...
}
if the resend button is tapped, I think you want to restart the timer, so ...
#IBAction func resendOtpButn(_ sender: Any) {
registerService()
startTimer()
}
You will probably also want to cancel the timer if your otp completes successfully, so in your success completion handler you can just do
self.optTimer?.invalidate()

Related

How to get Scrubbing to work on UISlider using AVAudioPlayer

So I'm using a rather unique version of audio player and everything works except for track scrubbing. The tracks, it should be noted, are buffered and collected from a website. I thought it might have to do with the observers I'm using. But I don't know if it's that really. I'm able to call the duration of the track and I'm able to get the actual UISlider to animate in conjunction with the track progression. But I can't scrub forward or backward. What ends up happening is I can move the slider but the track's progress doesn't move along with it. Here's what I've got:
override func viewDidLoad() {
audio.initialize(config: ["loop": true]) { (playerItem, change) in
var duration = CMTimeGetSeconds(playerItem.duration)
self.scrubSlider.maximumValue = Float(duration)
}
scrubSlider.setThumbImage(UIImage(named: "position_knob"), for: .normal)
}
override func viewDidAppear(_ animated: Bool) {
scrubSlider.value = 0.0
print(audio.seekDuration())
self.scrubSlider.maximumValue = audio.seekDuration()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.trackAudio), userInfo: nil, repeats: true)
print(self.scrubSlider.maximumValue)
}
#objc func trackAudio() {
var currentTime = audio.seekCurrentTime()
print(currentTime)
scrubSlider.value = currentTime
var duration = audio.seekDuration()
let remainingTimeInSeconds = duration - currentTime
timeElapsedLabel.text = audio.createTimeString(time: currentTime)
timeRemainingLabel.text = audio.createTimeString(time: remainingTimeInSeconds)
}
#IBAction func scrubberValueChanged(_ sender: UISlider) {
print("Entro")
var currentTime = (CMTimeGetSeconds((audio.playerQueue?.currentItem!.currentTime())!))
currentTime = (Float64)((scrubSlider.value))
}
Here's the observer where I collect the duration:
/**
Setup observers to monitor playback flow
*/
private func setupObservers(completion: #escaping (AVPlayerItem, Any) -> ()) {
// listening for current item change
self.audioQueueObserver = self.playerQueue?.observe(\.currentItem, options: [.new]) { [weak self] (player, _) in
print("media item changed...")
print("media number ", self?.playerQueue?.items() as Any, self?.playerQueue?.items().count as Any, self?.playerQueue?.currentItem as Any)
// loop here if needed //
if self?.audioPlayerConfig["loop"] as! Bool == true && self?.playerQueue?.items().count == 0 && self?.playerQueue?.currentItem == nil {
self?.playerQueue?.removeAllItems()
self?.playerQueue?.replaceCurrentItem(with: nil)
for item:AVPlayerItem in (self?.AVItemPool)! {
item.seek(to: CMTime.zero)
self?.playerQueue?.insert(item, after: nil)
}
self?.playerQueue?.play()
}
}
// listening for current item status change
self.audioQueueStatusObserver = self.playerQueue?.currentItem?.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in
guard let currentItemDuration = self.playerQueue?.currentItem?.duration else { return }
if playerItem.status == .readyToPlay {
print("current item status is ready")
print("media Queue ", self.playerQueue?.items() as Any, self.playerQueue?.items().count as Any)
print("item duration ", CMTimeGetSeconds(playerItem.duration))
print("itemD ", CMTimeGetSeconds(currentItemDuration))
print(currentItemDuration)
completion(playerItem, change)
}
})

How do I cancel a delayed action if the same button that triggered it is clicked before the delayed time is executed?

class viewcontroller1: UIViewController {
I have these two images. When the next button is clicked, image1 changes to image2 after 10 seconds. However, I have no idea how to cancel/reset the delayed action if the next button is clicked again before the 10 seconds has expired. This is the code I have so far...
var image1: UIImageView!
var image2: UIImageView!
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
#IBAction func nextbutton(_ sender: Any) {
image1.image = UIImage(named: "image1")
delay(10) { self.image2.image = UIImage(named: "image2")
}
}
}
I would really appreciate any help. Thanks
anyncAfter can not be canceled
As per comment you have to use Timer following is example
Create global variable
var timer:Timer?
and On action
#IBAction func btnImageChangedTapped(_ sender: UIButton) {
if (self.timer != nil) {
self.timer?.invalidate()
self.timer = nil;
}
timer = Timer.scheduledTimer(timeInterval: yourTime, target: self, selector: #selector(changeImage:), userInfo: sender, repeats: false)
}
u can try this one
var timer : Timer?
func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(self.YourImageChangeAction), userInfo: nil, repeats: false)
}
func resetTimer(){
timer?.invalidate()
startTimer()
}
add func resetTimer to your UiButton sender.
You could use a click counter in your VC and test it upon execution of the delayed code.
for example:
var clickCount = 0
#IBAction func nextbutton(_ sender: Any)
{
clickCount += 1
let wantsClickCount = clickCount
image1.image = UIImage(named: "image1")
delay(10)
{
guard self.clickCount == wantsClickCount
else { return }
self.image2.image = UIImage(named: "image2")
}
}
if the button was clicked again before the closure executes, the clickCount will no longer match and the timed code will do nothing.
This would even work with very fast clicking. Note that it will push off the image change by 10 seconds each time so the image change will always happen 10 seconds after the last click.

CXCallObserver is not working properly and App getting crash when running the app more than one (when includes contacts image data)

I am facing two major problem first one is :
1. I am trying to detect incoming call, outgoing call , dialing call for this i am using this code :
import UIKit
import CoreTelephony
import CallKit
class ViewController: UIViewController,CXCallObserverDelegate {
let callObserver = CXCallObserver()
var seconds = 0
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
callObserver.setDelegate(self, queue: nil)
}
override func viewWillAppear(_ animated: Bool) {
print("viewWillAppear \(seconds)")
}
fileprivate func runTimer(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds += 1
print("Seconds \(seconds)")
}
#IBAction func callButton(_ sender: UIButton) {
if let url = URL(string: "tel://\(12345879)"){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded == true {
print("Disconnected")
seconds = 0
self.timer.invalidate()
}
if call.isOutgoing == true && call.hasConnected == false {
print("Dialing call")
self.runTimer()
}
if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {
print("Incoming")
}
if call.hasConnected == true && call.hasEnded == false {
print("Connected")
}
}
}
It working fine when i am dialing a number it shows "Dialling" but when i cut the call then it shows "Disconnected" then again "Dialing" State.
Another problem is when i am fetching all contacts information from the device it works fine when i am not fetching imageData but when i am fetching contacts image it works fine for the very first time . Then if i run it again app become slow . then next it crash shows found nil while unwrapping a value.
i wrote my contact data fetching function in AppDelegate . it is calling when the app start . this is the code :
func fetchContactList(){
let loginInformation = LoginInformation()
var contactModelData: [ContactsModel] = []
var profileImage : UIImage?
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: {
granted, error in
guard granted else {
let alert = UIAlertController(title: "Can't access contact", message: "Please go to Settings -> MyApp to enable contact permission", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
return
}
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey,CNContactThumbnailImageDataKey] as [Any]
let request = CNContactFetchRequest(keysToFetch: keysToFetch as! [CNKeyDescriptor])
var cnContacts = [CNContact]()
do {
try store.enumerateContacts(with: request){
(contact, cursor) -> Void in
cnContacts.append(contact)
}
} catch let error {
NSLog("Fetch contact error: \(error)")
}
for contact in cnContacts {
let fullName = CNContactFormatter.string(from: contact, style: .fullName) ?? "No Name"
var phoneNumberUnclean : String?
var labelofContact : String?
var phoneNumberClean: String?
for phoneNumber in contact.phoneNumbers {
if let number = phoneNumber.value as? CNPhoneNumber,
let label = phoneNumber.label {
let localizedLabel = CNLabeledValue<CNPhoneNumber>.localizedString(forLabel: label)
print("fullname \(fullName), localized \(localizedLabel), number \(number.stringValue)")
phoneNumberUnclean = number.stringValue
labelofContact = localizedLabel
}
}
if let imageData = contact.imageData {
profileImage = UIImage(data: imageData)
print("image \(String(describing: UIImage(data: imageData)))")
} else {
profileImage = UIImage(named: "user")
}
self.contactModelData.append(ContactsModel(contactName: fullName, contactNumber:phoneNumberUnclean!, contactLabel: labelofContact!, contactImage: profileImage!, contactNumberClean: phoneNumberUnclean!))
}
self.loginInformation.saveContactData(allContactData: self.contactModelData)
})
}
I have solved this two problems using this :
for number one when i disconnect a call then if unfortunately it goes to "Dialling" option again i checked the "seconds" variable's value if it greater than 0 in "Dialing" then invalidate the thread.
for number two problem :
I used Dispatch.async.main background thread and take the thumbnail image

How can I disable a button for a few seconds after it is tapped

The following is the code for an IBAction I would like to disable for 5 seconds after it is clicked.
#IBAction func postPressed(sender: AnyObject) {
//var disableMyButton = sender as? UIButton
//disableMyButton!.enabled = false
//NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector:"enableButton", userInfo: nil, repeats: false)
if(currLocation != nil){
let testObject = PFObject(className: "Hey")
testObject["text"] = self.postView.text
testObject["count"] = 0
testObject["replies"] = 0
testObject["location"] = PFGeoPoint(latitude: currLocation!.latitude , longitude: currLocation!.longitude)
testObject["comments"] = []
testObject["user"] = PFUser.currentUser()
testObject.saveInBackgroundWithBlock({ (success, error) -> Void in
if success {
self.dismissViewControllerAnimated(true , completion: nil)
if let completion = self.completion {
completion(self)
}
}
})
} else {
alert()
}
}
The commented out code is my attempt at disabling the button for 5 seconds, but this code crashes, and I believe it crashed because there is no reference to the enable button.
Typically I would do something like
func enableButton() {
self.button.enabled = true
}
But this won't work since it's an IBAction and theres no reference to the button besides there. Maybe there is a way to do so, but I'm at a loss.
Thanks in advance for some help.
Try this,
while tap the action set button disable.
set some interval to enable
your button by using delay function.
Ex:
let tapGesture = UITapGestureRecognizer(target: self, action: "tapaction")
tapGesture.numberOfTapsRequired = 1
Your_Button_name.addGestureRecognizer(tapGesture)
#IBAction func tapaction()
{
Your_Button_name.disable=true
//Delay function to enable your button
NSTimer.scheduledTimerWithTimeInterval(Your_time_value_delay, target: self, selector: Selector("enablefunc"), userInfo: nil, repeats: false)
}
func enablefunc()
{
Your_Button_name.disable=false
}
SWIFT 5.2
within UIButton Action place this code:
yourButtonOutlet.isEnabled = false
//Delay function to enable your button for 3 seconds
Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.enablefunc), userInfo: nil, repeats: false)
Then outside of the UIButton func put:
#objc func enablefunc()
{
yourButtonOutlet.isEnabled = true
}
var disableMyButton = sender as? UIButton
disableMyButton!.enabled = false
testObject.saveInBackgroundWithBlock({ (success, error) -> Void in
if success {
self.dismissViewControllerAnimated(true , completion: nil)
if let completion = self.completion {
completion(self)
}
} else {
disableMyButton!.enabled = true
}
}
Also, depending on what completion(self) does, you may want to do it before you dismiss the view controller. I dunno.
you can use the dispatch framework to add a delay. Use the main thread.
disableMyButton!.enabled = false
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
disableMyButton!.enabled = true
}

Running a timer when the phone is sleeping

I'm building an app and I need a timer to run if the user sends the screen to the background, or if they put the phone in sleep and open it again. I need the timer to still be going.
I tried recording the time when I exit the and enter it again, subtracting the two and adding that to the running count, and it seems to work fine on the Xcode simulator but when I run it on my phone it doesn't work. Any ideas?
Here is the code for reference.
And the timer starts with a button I didn't include that part but it's just a simple IBAction that calls the timer.fire() function.
var time = 0.0
var timer = Timer()
var exitTime : Double = 0
var resumeTime : Double = 0
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
exitTime = Date().timeIntervalSinceNow
}
override func awakeFromNib() {
super.awakeFromNib()
resumeTime = Date().timeIntervalSinceNow
time += (resumeTime-exitTime)
timer.fire()
}
func startTimer() {
if !isTimeRunning {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:
#selector(WorkoutStartedViewController.action), userInfo: nil, repeats: true)
isTimeRunning = true
}
}
func pauseTimer() {
timer.invalidate()
isTimeRunning = false
}
#objc func action()
{
time += 0.1
timerLabel.text = String(time)
let floorCounter = Int(floor(time))
let hour = floorCounter/3600
let minute = (floorCounter % 3600)/60
var minuteString = "\(minute)"
if minute < 10 {
minuteString = "0\(minute)"
}
let second = (floorCounter % 3600) % 60
var secondString = "\(second)"
if second < 10 {
secondString = "0\(second)"
}
if time < 3600.0 {
timerLabel.text = "\(minuteString):\(secondString)"
} else {
timerLabel.text = "\(hour):\(minuteString):\(secondString)"
}
}
You do have the right idea but the first problem I see is that viewWillDissapear is only called when you leave a view controller to go to a new viewController - It is not called when the app leaves the view to enter background (home button press)
I believe the callback functions you are looking for are UIApplication.willResignActive (going to background) and UIApplication.didBecomeActive (app re-opened)
You can access these methods in the AppDelegate or you can set them up on a view controller heres a mix of your code and some changes to produce a working sample on one initial VC:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
var time = 0.0
var timer = Timer()
var exitTime : Date? // Change to Date
var resumeTime : Date? // Change to Date
var isTimeRunning = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
startTimer()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
// Add willResign observer
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillResign),
name: UIApplication.willResignActiveNotification,
object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
// Remove becomeActive observer
NotificationCenter.default.removeObserver(self,
name: UIApplication.didBecomeActiveNotification,
object: nil)
// Remove becomeActive observer
NotificationCenter.default.removeObserver(self,
name: UIApplication.willResignActiveNotification,
object: nil)
}
func startTimer() {
if !isTimeRunning {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:
#selector(self.action), userInfo: nil, repeats: true)
isTimeRunning = true
}
}
#objc func action() {
time += 0.1
timerLabel.text = String(time)
let floorCounter = Int(floor(time))
let hour = floorCounter/3600
let minute = (floorCounter % 3600)/60
var minuteString = "\(minute)"
if minute < 10 {
minuteString = "0\(minute)"
}
let second = (floorCounter % 3600) % 60
var secondString = "\(second)"
if second < 10 {
secondString = "0\(second)"
}
if time < 3600.0 {
timerLabel.text = "\(minuteString):\(secondString)"
} else {
timerLabel.text = "\(hour):\(minuteString):\(secondString)"
}
}
#objc func applicationDidBecomeActive() {
// handle event
lookForActiveTimers()
}
func lookForActiveTimers() {
var timers = [NSManagedObject]()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Timers")
//3
do {
timers = try managedContext.fetch(fetchRequest)
print("timers: \(timers)")
var activeTimer: NSManagedObject?
for timer in timers {
if let active = timer.value(forKey: "active") as? Bool {
if active {
activeTimer = timer
}
}
}
if let activeTimer = activeTimer {
// Handle active timer (may need to go to a new view)
if let closeDate = activeTimer.value(forKey: "appCloseTime") as? Date {
if let alreadyTimed = activeTimer.value(forKey: "alreadyTimed") as? Double {
let now = Date()
let difference = now.timeIntervalSince(closeDate)
// Handle set up again here
print("App opened with a difference of \(difference) and already ran for a total of \(alreadyTimed) seconds before close")
time = alreadyTimed + difference
startTimer()
}
}
} else {
print("We dont have any active timers")
}
// Remove active timers because we reset them up
for timer in timers {
managedContext.delete(timer)
}
do {
print("deleted")
try managedContext.save() // <- remember to put this :)
} catch {
// Do something... fatalerror
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
#objc func applicationWillResign() {
// handle event
saveActiveTimer()
}
func saveActiveTimer() {
if isTimeRunning {
// Create a new alarm object
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
if let entity = NSEntityDescription.entity(forEntityName: "Timers", in: context) {
let newTimer = NSManagedObject(entity: entity, insertInto: context)
newTimer.setValue(true, forKey: "active")
let now = Date()
newTimer.setValue(now, forKey: "appCloseTime")
newTimer.setValue(self.time, forKey: "alreadyTimed")
do {
try context.save()
print("object saved success")
} catch {
print("Failed saving")
}
}
}
}
}
EDIT - Here is the full tested and working code on xCode 11.3 and a physical device iOS 13.2 - You have to figure out how to start and stop the timer according to your buttons - but this example simply starts the timer when the app is first opened and never stops or resets it.
You can reproduce this by creating a new single-view xCode project and replacing the code in the first view controller that it creates for you with the code above. Then create a label to attach to the outlet timerLabel on the VC
Also make sure to enable CoreData in your project while creating your new project * Then set up the entities and attributes in the xcdatamodel file:
Hope this helps

Resources