I know there are a lot of answers out there about similar issues, but it seems all of them are don't fit to my problem. And I am new to Swift and my head is burning about all this stuff.
The task is very simple. I have a ViewController and a Class called "TimeIsTicking", which I defined in a separate Swift File. I did this, because I want to feed 5 ViewController (which are nested in TabBarController) with data from the Timer and they all have to run "synchronized".
The function fireTimer puts every second the value of 1 to the variable seconds.
In the ViewController is a Label and I want the Label to be updated every time when the timer puts a new value to seconds.
dayLabelText gets the data, here is everything fine, but from that point I'm stuck. Label Text isn't been updated.
I suspect, that there has to be a "loop" to reload the data for the Label and I thought the loop in fireTimer would be enough but I was obviously wrong.
I tried the "Observer Thing" and the "Dispatchqueue Thing" but I didn't play well ((obviously).
Help would be much appreciated.
Here is the code of the timer class:
import Foundation
import UIKit
class TimeIsTicking {
var seconds: Int = 0
static let timeFlow = TimeIsTicking()
func fireTimer() {
let finance = FinanceVC()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.seconds += 1
finance.dayLabelText = "\(self.seconds)"
print("Seconds: ", self.seconds)
print("LabelText: ", finance.dayLabelText)
}
}
}
And here is the code of the VieWController:
import UIKit
class FinanceVC: UIViewController {
#IBOutlet weak var dayLabel: UILabel!
var dayLabelText = String(TimeIsTicking.timeFlow.seconds)
override func viewDidLoad() {
super.viewDidLoad()
dayLabel.text = dayLabelText
TimeIsTicking.timeFlow.fireTimer()
}
// Do any additional setup after loading the view.
}
I see a few issues:
The finance object goes out of scope when fireTimer() returns. It will be deallocated then. The timer will be setting a label text on a no longer existing view controller.
Instantiating a UIViewController with FinanceVC() doesn't display it on screen. After instantiating you need to explicitly show. You can do this for example by calling present(_:animated:completion:) from a parent view controller.
The timer updates dayLabelText which does not update dayLabel.text
Might be good to follow a basic YT tutorial on how to display a view controller.
Good luck, you'll get it soon enough!
Related
I'm trying to keep a timer running even if I switch view controllers. I played around with the Singleton architecture, but I don't quite get it. Pushing a new view controller seems a little easier, but when I call the below method, the view controller that is pushed is blank (doesn't look like the view controller that I created in Storyboards). The timer view controller that I'm trying to push is also the second view controller, if that changes anything.
#objc func timerPressed() {
let timerVC = TimerViewController()
navigationController?.pushViewController(timerVC, animated: true)
}
You need to load it from storyboard
let vc = self.storyboard!.instantiateViewController(withIdentifier: "VCName") as! TimerViewController
self.navigationController?.pushViewController(timerVC, animated: true)
Not sure if your problem is that your controller is blank or that the timer resets. Anyway, in case that you want to keep the time in the memory and not deallocate upon navigating somewhere else I recommend you this.
Create some kind of Constants class which will have a shared param inside.
It could look like this:
class AppConstants {
static let shared = AppConstants()
var timer: Timer?
}
And do whatever you were doing with the timer here accessing it via the shared param.
AppConstants.shared.timer ...
There are different parts to your question. Sh_Khan told you what was wrong with the way you were loading your view controller (simply invoking a view controller’s init method does not load it’s view hierarchy. Typically you will define your view controller’s views in a storyboard, so you need to instantiate it from that storyboard.)
That doesn’t answer the question of how to manage a timer however. A singleton is a good way to go if you want your timer to be global instead of being tied to a particular view controller.
Post the code that you used to create your singleton and we can help you with that.
Edit: Updated to give the TimeManager a delegate:
The idea is pretty simple. Something like this:
protocol TimeManagerDelegate {
func timerDidFire()
}
class TimerManager {
static let sharedTimerManager = TimerManager()
weak var delegate: TimeManagerDelegate?
//methods/vars to manage a shared timer.
func handleTimer(timer: Timer) {
//Put your housekeeping code to manage the timer here
//Now tell our delegate (if any) that the timer has updated.
//Note the "optional chaining" syntax with the `?`. That means that
//If `delegate` == nil, it doesn't do anything.
delegate?.timerDidFire() //Send a message to the delegate, if there is one.
}
}
And then in your view controller:
//Declare that the view controller conforms to the TimeManagerDelegate protocol
class SomeViewController: UIViewController, TimeManagerDelegate {
//This is the function that gets called on the current delegate
func timerDidFire() {
//Update my clock label (or whatever I need to do in response to a timer update.)
}
override func viewWillAppear() {
super.viewWillAppear()
//Since this view controller is appearing, make it the TimeManager's delegate.
sharedTimerManager.delegate = self
}
Problem: Initially my count is correct, however, adding a new array does not update my total array count label.
I have a View Controller (VC) that shows a label with a count of total arrays.
I have a Table View Controller (TVC) that arrays are added or deleted from.
I have a separate struct file to hold the arrays.
The label populates with the correct number of arrays when I run it. However, adding a new array item (via TVC) in the simulator does not update the label on the VC.
Questions:
Should I put the count in a function in VC? It is currently in the viewDidLoad()
Should I then call the function in TVC when adding or deleting an array?
If 2 is yes then do I reference the function in VTC? Would it simply be
updateCount() or do I have to reference the VC controller e.g.
ViewController.updateCount()
I have tried both ways but it does not seem to work, if I can get confirmation that would be great then I can make it work.
Thank you in Advance
My suggestion is bad but it works.
The easiest answer is use static variable, but it will cost memory phone. example code:
import Foundation
import UIKit
class VC: UIViewController {
static var count = 0
let label: UILabel = UILabel()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label.text = String(VC.count)
}
}
class TVC: UITableViewController {
func updateCount() {
VC.count = VC.count + 1
}
}
Whenever function updateCount() called in TVC, and go back to VC class it will update the count.
My other suggestion is use MVVM pattern that sending VCViewModel as parameter to TVC. and update count in VCViewModel.
Other suggestion is use delegate pattern.
If you do something like this:
var x = 0
var y = x
x = 7
print(y) //y is still 0
You need to update the count whenever you change the amount of arrays. To call a view controller's function from another view controller, you need to use a protocol. I suggest you research how to make protocols in swift - I would help you but it's hard without being able to see the code.
While I am pretty sure of a simple solution to this issue, the issue here is that the secondVC is in no way or form separate from the firstVC. The secondVC is what you get when you tap on the tableViewCell. The cell you tapped (using the indexPath) I send some data to the secondVC and then you can edit that task in the secondVC and even set a notification reminder for it.
In the secondVC I am running some code that triggers a timer at the time set by the user for that particular task. The timer in return invokes a selector method that updates a label and changes it to "Time's Up".
When this label is "Time's Up", I set the bellicon which is in the tableviewcell in the firstVC to white or remove the bellicon from the particular task.
I want this peice of code to run even if I am not in the secondVC. (I have this code running in viewDidAppear). If I put this code in a function and then call it while I am in the firstVC, the code has no idea which time it should select.
override func viewWillAppear(_ animated: Bool) {
adjustTextViewHeight()
edittaskview.becomeFirstResponder()
cardremindery?.constant = -999
guard let selectedDate = editnotes?.sSelectedDate,
var needsToRemind = editnotes?.sReminderState else {
print("No date selected")
return
}
if (needsToRemind) {
self.timesUpTimer = Timer(fireAt: selectedDate, interval: 0, target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: false)
RunLoop.main.add(timesUpTimer!, forMode: RunLoopMode.commonModes)
}
}
Selector code that updates the belliconcolor:
#objc func updateTimeLabel()
{
editnotes?.sReminderDate = "Time's up"
editnotes?.belliconcolor = .white
reminderMsg.text = editnotes?.sReminderDate
print("Time is up ok")
}
The above code works when you are in the secondVC. I want this to work even if I am in the firstVC. Since I am using FetchedResultsController, I am pretty sure that the tableView will be updated if data is changed and since the belliconcolor is an element I have saved in coreData changing it will update the tableview.
The question is: How can I run this piece of code from the firstVC for all the elements of the tableview?
The bellicon appear next to those tasks that you set reminder for in the secondVC. You access secondVC by tapping the relevant cell in the tableview:
You set the reminder in this secondVC. If the the time is up then the label underneath Due Date changes to 'Time's Up' and the belliconcolor for the specific task is changed to .white
Time's Up! -> The piece of code has been excecuted and the belliconcolor for this particular task is .white. This will be apparent when I go to the firstVC. I want this code to run even if I am in the firstVC so that the tableview is automatically updated.
Usually, there are 2 ways of doing it:
Use NotificationCenter.
Use Delegate pattern.
Both of them may work. How to choose will be a right question. To answer this may help.
Usually, if I want to have only 1 connection between 2 objects I usually go with Delegate. If you want to Notify many objects in your app then use NotificationCenter.
Hope it helps. Let me know if something isn't clear I will try to explain.
UPDATE:
Read it once more and yes Notifications/Delegate from the SecondVC won't work if you leave that screen. Leaving the screen will deinit everything you had there. Therefore you need some, for example, singleton class that will be hold in the memory all the time. So. from the SecondVC you set the timer into the singleton and singleton later will fire the Notifications/Delegates to FirstVC (or anywhere else you may need it).
I found an excellent discussion here on stackoverflow that presents and explains many solutions to the type of questions you have, Passing Data between View Controllers
I ran into a bizarre bug earlier this week and wanted to follow up to see how to prevent the root cause of the issue.
Take the following code.
//*****************************
//MAINVIEWCONTROLLER CLASS CODE
//*****************************
//Some event happens that triggers me to want to load up TestViewController.
func showViewController(){
var testController = TestViewController()
testController.someMethod("Test1")
self.navigationController?.pushViewController(testController, animated: true)
}
//*****************************
//TESTVIEWCONTROLLER CLASS CODE
//*****************************
testView:TestView!
override func viewDidLoad(){
super.viewDidLoad()
testView = TestView()
...
}
func someMethod(someData:String){
testView.name = someData //AppCrashes here because testView might be nil.
...
}
So someMethod is getting fired before TestViewController has had the chance to go through and create the testView. I'm then getting a cannot unwrap an optional value because testView is nil and I'm accessing a property on it.
Whats strange is the application I'm running probably does this exact thing in 6 different places, and 5/6 are working perfectly fine, but 1/6 is now giving me this error. I'm guessing its because of the viewDidLoad not being guaranteed to fire immediately or complete before someMethod is executed, but why then is this not happening on all 6 of the use cases.
So my main questions are:
Why does this crash happen?
What is the best practice to avoid it.
Thanks! Thoughtful answers will get up-votes as always! Let me know if any more info would be helpful.
Basically never run code in the destination controller called from the source controller which involves UI elements. Create a property, set it in the source controller and assign the value to the UI element in viewDidLoad() of the destination controller, for example:
//*****************************
//MAINVIEWCONTROLLER CLASS CODE
//*****************************
//Some event happens that triggers me to want to load up TestViewController.
func showViewController(){
var testController = TestViewController()
testController.someData = "Test1"
self.navigationController?.pushViewController(testController, animated: true)
}
//*****************************
//TESTVIEWCONTROLLER CLASS CODE
//*****************************
testView:TestView!
var someData = ""
override func viewDidLoad(){
super.viewDidLoad()
testView = TestView()
testView.name = someData
...
}
viewDidLoad is called when the ViewController completes loading in preparation to be shown, e.g. when a segue takes place or when involved in a present.
As written your code shouldn't even compile since testView is optional, but you have two options. Use optionals (in which case the view may not get the information if not called after viewDidLoad, but it won't crash) or store the passed information and update your view in viewDidLoad or viewWillAppear.
Something you might want to be aware of is viewIfLoaded
You can force the viewDidLoad method with with:
var testViewController = TestViewController()
_ = testViewController.view
testViewController.someMethod("Test")
Initializing the ViewController doesn't automatically call viewDidLoad
Something very strange has been happening when I change my UILabel's text in my view controller's viewDidLoad method. Although I am 100% certain that the label is connected (I have reconnected it multiple times as well as changed the name), it still gives me an EXC_BAD_EXCEPTION error when trying to change it. My code is below.
**NOTE: I should also mention that this error does not occur when the VC first initializes, but when I press a button that segues to another VC.
class BroadwayOrderReview: UIViewController, UITableViewDelegate, UITableViewDataSource, ClassNameDelegate {
#IBOutlet weak var BroadwayOrderReviewTableView: UITableView!
#IBOutlet weak var finalOrderPriceTotalLbl: UILabel!
var OrderDictionary: [String:String] = [:]
func addButtonAction(addedList:[String:Float],numOrders:[String:Int]) {
print(addedList)
print(numOrders)
}
override func awakeFromNib() {
}
override func viewDidLoad() {
super.viewDidLoad()
print("NUM ORDERS \(numOrders)")
self.finalOrderPriceTotalLbl.text = "0.00"
let totalPriceArray = Array(numOrders.keys).sort(<)
for (key) in totalPriceArray {
print("TOTAL PRICE ARRAY \(totalPriceArray)")
self.finalOrderPriceTotalLbl.text = String(Float(self.finalOrderPriceTotalLbl.text!)! + (Float(numOrders[key]!) * addedList[key]!))
print("TOTAL ORDER LBL \(finalOrderPriceTotalLbl.text)")
}
BroadwayOrderReviewTableView.delegate = self
BroadwayOrderReviewTableView.dataSource = self
for (name,orders) in numOrders {
print(name)
OrderDictionary["\(numOrders[name]!) \(name)"] = String(addedList[name]! * Float(numOrders[name]!))
}
print(OrderDictionary)
}
Thank you for any and all help, I really appreciate it.
You should not use a capital for naming your tableView.
BroadwayOrderReviewTableView should be broadwayOrderReviewTableView. You only want to use class, struct with first letter caps.
Did you try to delete your label from your code and storyboard?
It looks like the connection isn't there.
With what you shared it seems it can't access a value because it hasn't been initialized.
Something i didn't know when I started learning dev. you can actually type in the console log.
type po finalOrderPriceTotalLbl (for print out finalOrderPriceTotalLbl)
You can only use that when your app is on standby with breakpoint, and maybe crashes (I forgot for one sec) to setup a breakpoint just click on the number's line. to remove just drag it out.
If the print give you nil. you know there is a issue with your label connection for sure.