Widget data doesn't update when scrolling - Swift - ios

I have a widget with data. When I launch today extension my widget data is updating and showing in real time. But when I scroll the notification center and return to my widget, it doesn't update. I tried several different methods but they didn't help me. I wrote below the last method which I tried.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "updateLabels", userInfo: nil, repeats: true)
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(.NewData)
}
func updateLabels() {
runtimeLabel.text = returnTime() + " " + returnDay()
}
func returnTimeInterval() -> NSTimeInterval {
let uptime = NSProcessInfo().systemUptime
return uptime
}
func returnTime() -> String {
dateFormatter.unitsStyle = .Short
dateFormatter.allowedUnits = [.Day, .Hour, .Minute, .Second]
dateFormatter.zeroFormattingBehavior = .Pad
let time = dateFormatter.stringFromTimeInterval(returnTimeInterval())!
return time
}
func returnDay() -> String {
dateFormatter.unitsStyle = .Short
dateFormatter.allowedUnits = [.Year, .Month, .Day]
dateFormatter.zeroFormattingBehavior = .Pad
let date = NSDate(timeInterval: -returnTimeInterval(), sinceDate: NSDate())
let formatter = NSDateFormatter()
formatter.locale = NSLocale.currentLocale()
formatter.dateStyle = .MediumStyle
let megaDate = formatter.stringFromDate(date)
return megaDate
}

I tried the same and it worked with the code below:
#IBOutlet weak var infoLabel: UILabel!
var timer = NSTimer()
var counter = 0
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateLabel"), userInfo: nil, repeats: true)
}
func updateLabel(){
counter += 1
myLabel.text = "Test \(counter)"
}
Update:
viewWillAppear and viewDidAppear should be called after when the widget is active and viewDidDisappear should be called when scrolling (leaving the widget). As for now the viewDidDisappear is working as expected but not viewWillAppear and viewDidAppear on scrolling.
It´s a known bug that this is not working properly, you can read more information in this post at Apples forum and check the bug status report here.

You should write your update code in widgetPerformUpdateWithCompletionHandler method.

Related

How to subtract current time from a target time?

I figured out how to display the live current time in "hh:mm:ss" (code below). I want the label to display the time left until a specific target time, like a countdown. I want it to count down until e.g. 3pm each day and then start over. I know I need to subtract my target time from the current time but I don't know how to do that.
(I am new to programming)
class ViewController: UIViewController {
#IBOutlet weak var Label: UILabel!
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
getCurrentTime()
}
private func getCurrentTime() {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:#selector(self.currentTime) , userInfo: nil, repeats: true)
}
#objc func currentTime() {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm:ss"
Label.text = formatter.string(from: Date())
}
}
Use timeIntervalSince to find TimeInterval between two dates. Like below
let secondsBetween: TimeInterval = targetDate.timeIntervalSince(currentDate)
self.counter = Int(secondsBetween)
func timerAction() {
counter -= 1
label.text = timeString(time: TimeInterval(counter))
}

I want to make datePicker on IOS by swift

all
I am a new developer on IOS. nowdays I study Swift by books.
there are some trouble. I use swift3 but the book consists of swift2.
so I don't know what is wrong code.
could you help me?
thanks you for reading and helping me.
this is datacode.
import UIKit
class ViewController: UIViewController {
let timeSelector: Selector = #selector(ViewController.updateTime)
let interval = 1.0
var count = 0
#IBOutlet weak var IbICurrentTime: UILabel!
#IBOutlet weak var IbIPickTime: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: timeSelector)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func changeDatePicker(_ sender: UIDatePicker) {
let datePickerView = sender
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd HH:mm:ss EEE"
IbIPickTime.text = "선택시간: " + formatter.string(from: datePickerView.date)
}
func updateTime() {
IbICurrentTime.text = String(count)
count = count+1
}
}
there are problem
Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: timeSelector)
}
i don't know what i have to input at "block"?
"Block" is the Objective-C term of Swift's closure. The "Blocks" variant works like this:
Timer.scheduledTimer(timeInterval: interval, repeats: true, block: { timer in
self.IbICurrentTime.text = String(self.count)
self.count += 1
})
You don't need to define a separate updateTime() function with the block syntax.
In Swift the above can be written more naturally as
Timer.scheduledTimer(timeInterval: interval, repeats: true) { _ in
self.IbICurrentTime.text = String(self.count)
self.count += 1
}
If you want to use selectors, use scheduled​Timer(time​Interval:​target:​selector:​user​Info:​repeats:​) instead:
Timer.scheduledTimer(time​Interval: interval,
target: self,
selector: #selector(updateTime),
userInfo: nil,
repeats: true)

Some App Actions Breaking TImer?

I have a Timer that i run on my app, it uses a save and reload feature for when the user leaves the view and returns to the view. However if i go to a different tab on my app, the actions needed for that tab seem to break the timer.
Would this be due to thread usage in the app and the coredata of the other tab breaking the timers counting? Here is my timer code
Update 2: Here is my revised timer code in full...
// MARK: - SETTING UP SETS & TIMERS
func createStopTimeForRestFromUserTime(userTime: Int) -> Date {
let calendar = Calendar.current
let stopDate = calendar.date(byAdding: .second, value: userTime, to: Date())
return stopDate!
}
func createTimer(stopDate: Date) {
print("CONSTRUCTING A TIMER")
userDefaults.set(stopDate, forKey: "setStopDate")
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateRestTimer), userInfo: nil, repeats: true)
}
func updateRestTimer() {
let presentTime = Date()
let stoppedTime = UserDefaults.standard.object(forKey: "setStopDate") as? Date
if stoppedTime?.compare(presentTime) == ComparisonResult.orderedDescending {
restRemainingCountdownLabel.text = dateComponentsFormatter.string(from: presentTime, to: stoppedTime!)
} else {
self.stopTimer()
print("THE TIMER IS NOW FINISHED")
}
}
func stopTimer() {
self.restTimer.invalidate()
}
// MARK: - CONFIGURE TIMER ON OPEN / CLOSE
override func viewWillDisappear(_ animated: Bool) {
print("VIEWWILLDISAPPEAR WAS CALLED")
stopTimer()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ViewDidChangeNotification"), object: NSDate())
}
func handleResumedTime(disappTime: NSDate) {
let disappearedTime = disappTime
let resumeTime = NSDate()
if disappearedTime.compare(resumeTime as Date) == ComparisonResult.orderedDescending {
print("RESUMING THE TIMER")
self.createTimer(stopDate: disappearedTime as Date)
} else {
print("TIMER HAS FINISHED")
stopTimer()
}
}
func handleTimerCallback(notification: NSNotification) {
if let date = notification.object as? NSDate {
self.handleResumedTime(disappTime: date)
}
}
Code in an alternate VC id switch between which is causing breakage:
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(RoutineController.handleTimerCallback(notification:)), name: NSNotification.Name(rawValue: "ViewDidChangeNotification"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ViewDidChangeNotification"), object: NSDate())
}
func handleTimerCallback(notification: NSNotification) {
let date = notification.object
print("Object is \(date)")
}
EDIT: Added the updated handleResumed func and console print out when changing tab and changing back again...
func handleResumedTime(disappTime: NSDate) {
let disappearedTime = disappTime as Date
let resumeTime = NSDate() as Date
print("ATTEMPTING RESUME")
print(disappearedTime)
print(resumeTime)
if resumeTime.compare(disappearedTime) == ComparisonResult.orderedAscending {
print("RESUMING THE TIMER")
self.createTimer(stopDate: disappearedTime)
} else {
print("TIMER HAS FINISHED")
stopTimer()
}
}
Try comparing the timers in milliseconds using DateFormatter and passing data between controllers using Notifications.
This function is called every time you switch tabs. It invalidates the timer, stops the ticking, and saves the time in milliseconds.
override func viewWillDisappear(_ animated: Bool) {
restTimer.invalidate()
let df = DateFormatter()
df.dateFormat = "y-MM-dd H:m:ss.SSSS"
let disappearingDate = Date()
let disappearingDateInMilliseconds = df.string(from: disappearingDate)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "kViewDidChangeExampleNotification"), object: disappearingDateInMilliseconds)
}
/* add your Notification observers in each view controller */
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleTimerCallback(notification:)), name: NSNotification.Name(rawValue: "kViewDidChangeExampleNotification"), object: nil)
}
You need to define the function that will handle the notifications in each controller:
func handleTimerCallback(notification: NSNotification) {
if let dateString = notification.object as? String {
print("Object is an String: \(notification.object)")
resumeTimerWithDate(dateString)
} else {
print("Object is not of type String: \(notification.object)")
}
}
Quick example comparing Dates in Swift by milliseconds:
func resumeTimerWithDate(dateString: String) {
let df = DateFormatter()
df.dateFormat = "y-MM-dd H:m:ss.SSSS"
let secondDate = Date()
let secondDateToStr = df.string(from: secondDate)
if dateString < secondDateToStr {
print("RESUMING THE TIMER")
let resumeDate = df.date(from: dateString)
self.createTimer(resumeDate)
} else {
print("TIMER HAS FINISHED")
}
}
Other things to consider: You may want to add some print statements for debugging / sanity check because some functions may be firing before others. For example, rather than posting the notification in viewWillDisappear, you can try posting it in viewDidDisappear, or using prepare(for segue:).

Call a function using NSTimer

So as the code shown below, the function loadView 3 is supposed to run 300 seconds(5 minutes) after the user tapped the button. But when I build and run, it does not. I also did a few experiments, I changed the timer to 5 seconds, it worked. So after the app is suspended from iOS system, the NSTimer doesn't run anymore? So what's the problem, how can I fix it?
#IBAction func buttonTapped(sender: AnyObject) {
NSTimer.scheduledTimerWithTimeInterval(300, target: self, selector: #selector(ViewController.loadView3), userInfo: nil, repeats: false)
createLocalNotification()
}
func createLocalNotification() {
let localnotification = UILocalNotification()
localnotification.fireDate = NSDate(timeIntervalSinceNow: 300)
localnotification.applicationIconBadgeNumber = 1
localnotification.soundName = UILocalNotificationDefaultSoundName
localnotification.alertBody = "Hello!"
UIApplication.sharedApplication().scheduleLocalNotification(localnotification)
}
func loadView3() {
label.text = "e89saa"
}
You could try something like this. The approach is, if timer works proceed with same logic, otherwise (probably app is killed or went to background), save firedDate and update UI before showing controller in method viewWillAppear.
#IBAction func buttonTapped(sender: AnyObject) {
NSTimer.scheduledTimerWithTimeInterval(300, target: self, selector: #selector(ViewController.loadView3), userInfo: nil, repeats: false)
createLocalNotification()
}
func createLocalNotification() {
let localnotification = UILocalNotification()
localnotification.fireDate = NSDate(timeIntervalSinceNow: 300)
localnotification.applicationIconBadgeNumber = 1
localnotification.soundName = UILocalNotificationDefaultSoundName
localnotification.alertBody = "Hello!"
UIApplication.sharedApplication().scheduleLocalNotification(localnotification)
// save in UserDefaults fireDate
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(localnotification.fireDate, forKey: "firedDate")
defaults.synchronize()
}
func loadView3() {
// reset in UserDefaults fireDate
let defaults = NSUserDefaults.standardUserDefaults()
defaults.removeObjectForKey("firedDate")
defaults.synchronize()
label.text = "e89saa"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated) // No need for semicolon
// retrieve fireDate from UserDefaults
let defaults = NSUserDefaults.standardUserDefaults()
let fireDate = defaults.objectForKey("firedData")
// check if we should update UI
if let _ = fireDate as? NSDate! {
if currentDate.compare(firedDate) == NSComparisonResult.OrderedDescending {
loadView3()
}
}
}

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