How to reload tableview from another view controller in swift - ios

When I dismiss a modal view controller I want the tableview to update, I am using the form sheet presentation style on iPad so the viewWillAppear and the viewDidAppear methods will not work

You can do this:
In your tableView Controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(notification: NSNotification){
//load data here
self.tableView.reloadData()
}
Then in the other ViewController :
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)

Swift 3 version code:
In your first view controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
func loadList(){
//load data here
self.tableView.reloadData()
}
In your second view controller:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)

I find the segue approach more elegant.
Let's say that we have ViewControllerA and a ViewControllerB. We are at ViewControllerB and we want from ViewControllerB to jump straight back to ViewControllerA and update the table view in ViewControllerA.
In ViewControllerA add the following action in your ViewController class:
#IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Yes, this code goes in the ViewController of the ViewController you want to go back to!
Now, you need to create an exit segue from the ViewControllerB's storyboard (StoryboardB). Go ahead and open StoryboardB and select the storyboard. Hold CTRL down and drag to exit as follows:
You will be given a list of segues to choose from including the one we just created:
You should now have a segue, click on it:
Go in the inspector and set a unique id:
In the ViewControllerB at the point where you want to dismiss and return back to ViewControllerA, do the following (with the id we set in the inspector previously):
self.performSegue(withIdentifier: "yourIdHere", sender: self)
Now, whenever you use the segue to return back to ViewControllerA, the ViewControllerA will update the TableView straightaway.

Missing comma on this line, should instead be:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadList:", name:"load", object: nil)

An alternate solution: override UIViewController's dismiss(animated:completion:) method on the view controller that manages the table view (and presents the other one modally), so you can reload the table then:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag, completion: completion)
self.tableView.reloadData()
}
Note: This should work even if you call dismiss(animated:completion:) on the modal view controller itself (a viable alternative), because ultimately the call gets relayed to the presenting view controller.
From the method's docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
(emphasis mine)

You can use NotificationCenter to update your tableview.
First add observer...
NotificationCenter.default.addObserver(self, selector: #selector(doThisWhenNotify(notification:)), name: NSNotification.Name(rawValue: "load"), object: nil)
func doThisWhenNotify(notification : NSNotification) {
let info = notificatio.userInfo
//update tableview
}
Post on other ViewController
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil, userInfo: [String : Any])

Related

Swift tableView.reloadData() not working inside completion handler

I have two view controllers, in controller 1 I have a table view and I am presenting the controller 2 like so:
#IBAction func addPOItemButtonPressed(_ sender: Any) {
let itemViewController = self.storyboard?.instantiateViewController(withIdentifier: "LPOsItemController") as! LPOsItemController
itemViewController.RequestID = self.RequestID
let navigationViewController = UINavigationController(rootViewController: itemViewController)
self.present(navigationViewController, animated: true, completion: nil)
}
When I dismiss controller 2 I am calling a method in controller 1 like so:
In Controller 1:
NotificationCenter.default.addObserver(self, selector: #selector(self.refreshDetails(notification:)), name:NSNotification.Name(rawValue: "refreshDetails"), object: nil)
#objc func refreshDetails(notification: NSNotification){
spLPOHeaderGet() { result in
self.tableView.reloadData()
}
}
In Controller 2
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refreshDetails"), object: nil)
dismiss(animated: true, completion: nil)
My problem is when I call the method in Controller 1, the table view does not reload the data, sometimes it does and sometimes it doesn't. The reload call is in a completion handler. Can someone tell me what I am doing wrong?
Thanks,
Posting to notification for this situation is not a good idea. You can use the delegate to communicate from ViewController 2 to controller 1.
if you want to identify an issue in the current implementation try to put a breakpoint on
self.tableView.reloadData()
and check this method is called. and if it is called does tableview is in memory when this called. There is a chance when the completion handler is called this tableView object is deallocted.

Update Label On Another View

I have two views with a label on one of them. On the second view, there is a button. What I want to achieve here is to be able to press the button and it updates the label on the first view. How do I do that? I can't access the IBOutlet from the second view. Is there something that I have to do to the IBOutlet to make it public etc?
You can use NSNotificationCenter for that.
First of all in your viewDidLoad method add this code in your firstViewController class:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshlbl:", name: "refresh", object: nil)
which will addObserver when your app loads.
And add this helper method in same viewController.
func refreshlbl(notification: NSNotification) {
lbl.text = "Updated by second View" //Update your label here.
}
After that in your secondViewController add this code when you dismiss your view:
#IBAction func back(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when you press back button from secondView then refreshlbl method will call from firstView.
use custom delegate method create a delegate in second view and access that view delegate function in first view. or use NSNotification or use NSUserdefaults

Refreshing Data when dismissing Modal

I have an reference to a managed object called selectedItem, I load the data in on view controller and have a modal segue (over current context) to another view to edit the title property of the selectedItem.
I expect a textLabel to refresh the data when dismissing the modal view but it does not. I have used the same method to add to the table data and it worked, because I use tableView.reloadData but how can I refresh the label data using the modal segue ? or basically have the label change to the new value.
detailsViewController
var selectedItem: Item!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.tableView.reloadData()
titleLabel.text = selectedItem.title
}
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = selectedItem.title
}
EditViewController
#IBAction func save(sender: AnyObject) {
selectedItem.title = editTitle.text
var error: NSError?
if context!.save(nil){}
context?.save(&error)
self.presentingViewController?.viewWillAppear(true)
self.dismissViewControllerAnimated(true, completion: {});
}
PS: I tried to do a work around by using another segue to go back to the first view but that crashed, does anybody know why ?
You can use a NSNotification, they are pretty handy for this sort of thing, here's an example of some generic usage:
Parent View Controller
In viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "udpateObject:", name: "udpateObject", object: nil)
In deinit:
NSNotificationCenter.defaultCenter().removeObserver(self, name: "udpateObject", object: nil)
Then you'll make a func that matches the selector of the observer:
func udpateObject(notification: NSNotification) {
// here you'll get the object you update in a different view
if let receivedObject = notification.object as? YOUR_OBJECT_DATA_TYPE {
self.ThisInstanceVariable = receivedObject
// Update any UI elements
}
}
Update View Controller
Wherever you update your data:
NSNotificationCenter.defaultCenter().postNotificationName("udpateObject", object: YOUR_UPDATED_OBJECT)

Send action to first responder doesn't work if it's not Initial View Controller

I'm trying to create sliding menu similar to Code Menu in SpringApp here (see CodeViewController):
https://github.com/MengTo/Spring/blob/master/SpringApp
It works just fine as long as SpringViewController is Initial View Controller.
If I create another ViewController and make it initial then minimizeView/maximizeView is never getting called:
UIApplication.sharedApplication().sendAction("minimizeView:", to: nil, from: self, forEvent: nil)
In this method "to:" is set to nil to use the first responder. So it looks like when SpringViewController stops being initial view controller it's no longer first responder.
How do I fix it to make minimizeView and maximizeView actions defined in SpringViewController always work?
I found a workaround using notification center.
In SpringViewController add:
override func viewDidLoad() {
super.viewDidLoad()
// Add observer in notification center
// It receives notifications from menu
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "minimizeView:",
name: "minimizeView",
object: nil)
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "maximizeView:",
name: "maximizeView",
object: nil)
}
In OptionsViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
// skipped ...
// Post notification
NSNotificationCenter.defaultCenter().postNotificationName("minimizeView", object: nil)
}
#IBAction func closeButtonPressed(sender: AnyObject) {
// skipped...
// Post notification
NSNotificationCenter.defaultCenter().postNotificationName("maximizeView", object: nil)
// skipped...
}
This makes maximizeView and minimizeView work properly. I wonder if the approach with first responder is better.
You need this for view controllers to be able to receive actions:
- (BOOL)canBecomeFirstResponder {
return YES;
}

IOS Swift handle global events

How can I handle global events triggered by the notification centre for example in my API class I fire an event if an error response is received e.g. (500). When that event is fired an UIAlert should be displayed on what ever view controller is active, or on logout the login view controller should be presented.
As far as I can see there is no easy way to get the current view controller in order to interact with it. (Note that my root view controller is NOT a navigation controller).
An alternative solution, that will work regardless of whether your view controllers are embedded in a UINavigationController or not, would be to subclass UIViewController. This class will handle receiving the NSNotification that an error occurred and will also handle displaying the alert:
class MyViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "errorOccured",
name: "ErrorNotification",
object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ErrorNotification", object: nil)
}
func errorOccured() {
// Present an UIAlertViewController or the login screen.
}
}
Now, any UIViewControllers that should display an alert when the error notification is posted just have to be a subclass of MyViewController. Just make sure, if you override viewWillAppear or viewWillDisappear, that you call super.viewWillAppear or super.viewWillDisappear.
Is this way too hard to get current view controller ( when not using navigation controller ) ?
// on your app delegate
getCurrentViewController(self.window!.rootViewController!)
func getCurrentViewController(viewController:UIViewController)-> UIViewController{
if let navigationController = viewController as? UINavigationController{
return getCurrentViewController(navigationController.visibleViewController)
}
if let viewController = viewController?.presentedViewController {
return getCurrentViewController(viewController)
}else{
return viewController
}
}
For BroadCast Notification
NSNotificationCenter.defaultCenter().postNotificationName("erro400", object: nil)
For Receive
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "ErroOccure", name: "erro400", object: nil)
}
func ErroOccure()
{
//present alert from here
// do whatever you want
}
You have to Remove Notification when you finish with it.
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

Resources