Force refresh on another ViewController component with swift - swift

I'm trying to pass a trigger between 2 view controller but i can't find something that works ...
I have the main controller with a NSTableView, source linked, View Base.
main Controller class :
class ViewController: NSViewController {
I have an array defined in Global variables.
on the viewDidLoad i add 2 elements in my array, then i use setDelegate and setDataSource.
It works fine.
myLib.myCoins = fillCoinsTable()
print ("My lib has \(myLib.myCoins.count) objects ")
mainCoinTable.setDelegate(self)
mainCoinTable.setDataSource(self)
I have a segue from the WINDOWS CONTROLLER to my second view Controller.
(It's a ToolBar button). The segue is "Sheet" kind.
This second controller allows me to add an element in my global variable arrays, with a button "Save" .
Code on the second controller
#IBAction func addCoinButton(sender: AnyObject) {
//We add the coin
let newCoin:coin = coin(n: textName.stringValue)
newCoin.Year = Int.init(textYear.stringValue)
myLib.myCoins.append(newCoin)
self.dismissController(self)
}
If i add a button on the main controller, to reloadData on the TableView, it works fine. The third element is added.
but i would like that to be automatic ....
I tried segue, but mine is not between view controller but with the windows controller.
Can you please help ?
Thanks,
Nicolas

I think, you want to add data from oneViewController and without pressing button, you want after navigation or pressing back button, you want to reload automatically tableview to show updated results, right.
If I'm not wrong then, this solution will work for you.
Follow this below steps:
Step 1 :
Add this code in your view controller, from which you want to add data in array or in database, instead of button click.
override func viewDidLoad()
{
super.viewDidLoad()
let backItem = UIBarButtonItem(title: "Back", style: .Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backItem
}
func goBack()
{
NSNotificationCenter.defaultCenter().postNotificationName("load", object: nil)
self.navigationController?.popViewControllerAnimated(true)
}
Step 2:
Now its final step.
Now add this below code, to your result view controller, where you want to automatically update the result.
func loadList(notification: NSNotification){
self.TableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadList:",name:"load", object: nil)
}
Now implement this code-stuff in your coding. Hope it works for you.

If you want the table to reload automatically when the View appears add this to viewWillAppear
self.tableView.performSelectorOnMainThread(#selector(UITableView.reloadData), withObject: nil, waitUntilDone: true)

Related

swift xcode iOS: can I re-use a loaded modal fullscreen view controller?

I have a storyboard with two view controllers. First one, VC_1, has one button that opens 2nd one - VC_2.
VC_2 also has a button that opens VC_1.
Both controllers have almost identical code:
class VC_1: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
print(“VC_1 loaded")
}
override func viewDidAppear(_ animated: Bool){ print(“VC_1 appeared") }
override func viewDidDisappear(_ animated: Bool){ print(“VC_1 disappeared") }
#IBAction func btnShowVC_2(_ sender: UIButton)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
secondVC = storyboard.instantiateViewController(identifier: “VC_2”)
secondVC.modalPresentationStyle = .fullScreen
show(secondVC, sender: self)
}
}
The difference is only in "VC_2" instead of "VC_1" in the 2nd controller code.
I have seen this View Controller creation code in Apple documentation and many other examples around the Internet.
When I press the button on the VC_1, I see in the debug window, that VC_2 is loaded and appeared, and VC_1 is disappeared. And same, of course, happens when I press the button on VC_2 - it disappears, and VC_1 is loaded again.
My questions are:
what happens with View Controller object after "viewDidDisappear" has been called? Does it really disappear from memory, or "disappear" only means "you cannot see it on the screen?". I do not see "viewDidUnload" in the documentation...
I suppose that "viewDidLoad" means that new View Controller object was created in memory. Is there any way to load the View Controller object only once, and then hide and show it without causing "viewDidLoad" to be called? I tried to do it with global variable "secondVC" but got "Application tried to present modally an active controller" error.
viewDidDisappear: called after the view is removed from the windows’
view hierarchy. No, View controller object just left the view property. By the way the amount of memory used by view controllers is negligible. So dont think about too much. If you want to catch when Your View controller object release from the memory put
deinit { print("vc deallocated") }
viewDidUnload, it has been deprecated since the iOS
6, which used to do some final cleaning.
Partly true. Keep in mind ViewDidload called one time for the life cycle of view controller. There is a method called before viewdidload but this is not related with your question.
In addition to "There is a method before viewdidload" -> loadView( ) is a method managed by the viewController. The viewController calls it when its current view is nil. loadView( ) basically takes a view (that you create) and sets it to the viewController’s view (superview).

Table view not updating when called from another View Controller using NotificationCenter

I have a table view in one view controller. I want to update the table view from another VC. I'm doing this using NotificationCenter like this:
In the table view VC:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.updateTableView), name: NSNotification.Name(rawValue: "updateTableView"), object: nil)
}
#objc func updateTableView() {
print("in func")
DispatchQueue.main.async {
self.listTV.reloadData()
}
}
In the opened VC:
func reloadTV() {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "updateTableView"), object: nil)
}
The print works, but the table view doesn't reload. I can only see it changing if I dismiss the VC with the table view and open it again.
I've followed the suggestions of other answers which is to use DispatchQueue, but that didn't make a difference. What am I missing?
Many thanks!
I would consider doing this by protocol and delegate instead of notifications...
This is how I do it.
When you call tableView.reloadData(). It has no effect if the tableView is hidden (isHidden == true). I believe the behavior is also the same when the tableView is effectively hidden by another ViewController (so effectively hidden).
Solution: A suggestion is to reload the tableView when the view is shown again. Few ways to do that.
one is in viewDidAppear (depends on how you transitioned between ViewControllers),
or
you can keep a reference to the tableView and after you dismiss the second ViewController (in which you were sending the notification) you provide the callback to reload the tableView. Roughly like the snippet, with self being the secondViewController you transitioned to. Dismiss that view
self.dismiss(animated: true, completion: {
self.tableView.reloadData()
}
or you can still use your notification and setup a flag and when the viewDidAppear, you then reloadData() inside viewDidAppear. However for this to work, you need to ensure that viewDidAppear is always called when the view is shown again (this will depend on how you transitioned between views).Simply override that method and print to verify.
There are other ways but the bottom line is tableView.reloadData() will be ignored if it is called when the tableView is not visible on the screen. My guess is it's an optimization made by Apple (no need to reload if no table is showing) or maybe a bug. I think they'll say it's a feature 😄
I suggest instead of using NotificationCenter, to instead set a Bool, tableViewNeedsUpdate, and check for the value of that Bool when the view with the UITableView is presented, and the UITableView is visible and able to accept reloadData() calls. After the reload is complete, set the variable back to false.

Programmatically press back button for UIViewController with UITableView iOS swift

I have a UIViewController that implements UITableViewDelegate, UITableViewDataSource and that contains a UITableView as a member variable. When a user click on one of the rows of that table, the app performs a storyboard segue to open the detail view controller. That detail view controller of course has a button in the top left of the screen that is the "back" button to go back up to the UIViewController with the UIViewTable.
So, suppose that I want to programmatically "click" that back button. How exactly would I do that in swift? This is the most recent version of swift (swift 4?) in XCode 10.1.
UPDATE:
So here is how I solved this. As the answers below show, it is possible to use self.navigationController?.popViewController(animated: true) to just return to the previous view controller. What I discovered I also wanted to do, however, was to call a specific method in that view controller so that it executed a certain behavior once it got shown. It turns out that is also possible, but in my case it was a bit tricky, since that prior view controller was actually a UITabBarController. Therefore I had to get the ViewController that I was interested in from the UITabBarController. I did it like this:
let numvc = navigationController!.viewControllers.count
let tvc:UITabBarController = navigationController!.viewControllers[numvc-2] as! UITabBarController
let my_vc: MyCustomVC = tvc.viewControllers![0] as! MyCustomVC
my_vc.some_function()
Here of course MyCustomV is my custom view controller class and some_function() is the method I want to call on that class. Hope this helps someone.
When You run a segue you perform a "pushViewController" method to the next view, so if you want to go back to the previous view programmatically you just have to do is pop the last view like so:
self.navigationController?.popViewController(animated: true)
UPDATE
You just need the if statement if you have multiple segues from that viewController, if not, you can delete and just cast the next view as you wish and set the properties, let the autocomplete write the *prepare(for segue... * method for you, so You don't run into any problems
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueName" {
let destinationVC = segue.destination as! CustomViewController
destinationVC.labelExample.text = "Some text I'm sending"
}
}
Are you sure you need to "click" the button?
If all you need is to dismiss details view controller, you can just call navigationController?.popViewController(animated: true)
Or if you want to deal directly with button, you can tell it to send its actions: backButton.sendActions(for: .touchUpInside)
Or if you absolutely need to show button clicking animation, then you will need something like this (you should play and choose suitable delay):
backButton.isHighlighted = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
backButton.isHighlighted = false
backButton.sendActions(for: .touchUpInside)
}

Where in view lifecycle to update controller after modal UIViewController dismissed

I have a UIViewController with a UILabel that needs to display either "lbs" or "kg". My app has a settings screen (another UIViewController) that is presented modally over the first view controller and the user can select either of the two units and save their preference. If the units are changed and the modal settings screen is dismissed, I of course want the label on the first view controller to be updated with the new units value (but without refreshing the whole view). I thought I knew how to make it work, but evidently I don't.
On my modal settings screen, I have a UISegmentedControl to allow the user to select units. Anytime it's changed, this function updates userDefaults:
func saveUnitsSelection() {
if unitsControl.selectedSegmentIndex == 0 {
UserDefaultsManager.sharedInstance.preferredUnits = Units.pounds.rawValue
} else {
UserDefaultsManager.sharedInstance.preferredUnits = Units.kilograms.rawValue
}
}
Then they would likely dismiss the settings screen. So, I added this to viewDidLoad in my first view controller:
override func viewDidLoad() {
super.viewDidLoad()
let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
units.text = preferredUnits
}
That didn't work, so I moved it to viewWillAppear() and that didn't work either. I did some research and some caveman debugging and found out that neither of those functions is called after the view has been loaded/presented the first time. It seems that viewWillAppear will be called a second time if I'm working within a hierarchy of UITableViewControllers managed by a UINavigationController, but isn't called when I dismiss my modal UIViewController to reveal the UIViewController underneath it.
Edit 1:
Here's the view hierarchy I'm working with:
I'm kinda stuck at this point and not sure what to try next.
Edit 2:
The user can tap a 'Done' button in the navigation bar and when they do, the dismissSettings() function dismisses the Settings view:
class SettingsViewController: UITableViewController {
let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
// some other variables set here
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.topItem?.title = "Settings"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissSettings(_:)))
if preferredUnits == Units.pounds.rawValue {
unitsControl.selectedSegmentIndex = 0
} else {
unitsControl.selectedSegmentIndex = 1
}
}
func dismissSettings(sender: AnyObject?) {
navigationController?.dismissViewControllerAnimated(true, completion: nil)
}
}
THE REAL PROBLEM
You misspelled viewWillAppear. You called it:
func viewWillAppear()
As far as Cocoa Touch is concerned, this is a random irrelevant function that hooks into nothing. You meant:
override func viewWillAppear(animated: Bool)
The full name of the first function is: "viewWillAppear"
The full name of the second function is: "viewWillAppear:animated"
Once you get used to this, the extreme method "overloading" that Cocoa Touch uses gets easier.
This is very different in other languages where you might at least get a warning.
The other lesson that everyone needs to learn when posting a question is: Include All Related Code!
Useful logging function I use instead of print or NSLog, to help find these things:
class Util {
static func log(message: String, sourceAbsolutePath: String = #file, line: Int = #line, function: String = #function, category: String = "General") {
let threadType = NSThread.currentThread().isMainThread ? "main" : "other"
let baseName = (NSURL(fileURLWithPath: sourceAbsolutePath).lastPathComponent! as NSString).stringByDeletingPathExtension ?? "UNKNOWN_FILE"
print("\(NSDate()) \(threadType) \(baseName) \(function)[\(line)]: \(message)")
}
}
[Remaining previous discussion removed as it was incorrect guesses]

Perform segue and don't show "Back" button on the destination view controller

I would like to seque programmatically to a "setup-VC" when the initial setup is not done. This works, BUT in this case I don't want to show the back button on the "setup-VC".
What I've done till now:
I've created two VC. (main-VC and setup-VC)
The code of main-VC:
...
if InitialSetupIsDone == true {
println("Loading the data...") //PPP
} else {
println("Segue to setup screen...") //PPP
self.performSegueWithIdentifier("segueToSetup", sender: self)
}
...
// Pull any data from the view controller which initiated the unwind segue.
#IBAction func segueToSetup(sender: UIStoryboardSegue)
{
let sourceViewController: AnyObject = sender.sourceViewController
}
In the main-VC I've created an unwind-segue ("EXIT"-action? - red icon at the top of the VC) and named it "segueToSetup".
At last point I've connected the "setup-VC" with the "main-VC" while dragging an segue from main-VC (yellow Icon top left) to the setup VC.
Then it would look like this:
(The upper connection goes directly from setup sign to the "setup-VC")
The goal for me now would be to kind of "hide" the back button in "setup-VC" while the initial setup is not done. Maybe I've don't create the unwind segue right... !? Thx
Write this line in destination VC viewDidLoad
navigationController?.navigationItem.setHidesBackButton(true, animated: true)
In viewWillAppaer method also write this
navigationController?.navigationItem.setHidesBackButton(true, animated: true)
This disabled the button for me:
let backButton = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: navigationController, action: nil)
navigationItem.leftBarButtonItem = backButton