Reload all UIViewControllers in each UINavigationController of UITabBar - ios

I have a tab based application and I want to update the information that is displayed in all UIViewControllers reloading them when I click in a button.
I have tried several ways to refesh the view controllers like [view setNeedsDisplay] and [view setNeedsLayout] but without success. I had also iterate all navigation controllers of tab bar and it's view controllers and applied this function calls without no success too.
Can anyone help me with this?

You basically add this to all controllers you want to refresh :
- (instancetype)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshAllViews:) name:#"RefreshAllViews" object:nil];
}
return self;
}
- (void)refreshAllViews:(NSNotification *)notification
{
// Reload all the data and views you want here
// eg. [self.tableView reloadData];
}
- (void)reloadAllViewsAction:(id)sender
{
// Call this from the button to refresh all views
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshAllViews" object:nil userInfo:nil];
}

Two options:
(1) Use notification, when user clicks the button, send a notification(Maybe with some informations), so that all the view controllers which are interested in that notification could update itself accordingly;
(2) Store the information in a shared model (Singleton) so it can be accessed by all the view controllers, and for each view controller, update UI in viewWillAppear according to that shared model

Related

Update all view controllers of a UITabBarController

I have a UITabBarController with four tabs. In each of the view controllers presented when a tab is selected I have a reset button. Tapping the button will change the appearance of all the view controllers. In particular, it will change the text of some labels in the different view controllers.
Is there some recommended way to update all the view controllers of a UITabBarController at the same time i.e. to make them reload their views?
My current approach is to make those view controllers conform to a protocol
#protocol XYReloadableViewController
- (void)reloadContents;
#end
and then send the message -reloadContents to all the view controllers when the button is tapped:
- (IBAction)touchUpInsideResetButton {
// ...
NSArray *viewControllers = self.tabBarController.viewControllers;
for (UIViewController<XYReloadableViewController> *viewController in viewControllers) {
[viewController reloadContents];
}
}
Then in each of the view controllers I would have to implement that method:
- (void)reloadContents {
[self.tableView reloadData];
// other updates to UI ...
}
But that seems a little too complicated. So is there an easier way to tell the view controllers to reload their views?
Edit: And what happens if I present a UINavigationController in some of the tabs or a container view controller? I would need to pass the message along the chain of all its child view controllers...
You can create ReloadViewController and all you contrlollers inheritance
from him.
ReloadViewController have property UIButton and methods:
-(void)reloadContents;
-(IBAction)touchUpInsideResetButton:(id)sender;
in .m file:
-(void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadContents)
name:#"MyNotification"
object:nil];
}
- (IBAction)touchUpInsideResetButton:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification"
object:nil];
}
in your viewControllers need only override method reloadContents
Notifications sound like a better fit for this. When view controllers need to be reset, broadcast an NSNotification and have any view controllers that might need to reset themselves listen for that notification, and trigger what they need to do. That way it doesn't matter how far down a navigation stack they are.
You might want to defer updates until the view actually appears. You could set a BOOL needsUpdate when the VCs receive the notification, but only do the actual update in viewWillAppear:, to save resources and prevent a large number of updates from going off at once (and perhaps blocking the main thread).
If this behaviour is common to all your view controllers, make a UIViewController subclass to prevent repeating code and have them all inherit from that. Alternatively, (if you're using Apple VC subclasses) make a category on UIViewController to add the notification methods.

How to go back to previous controller and refreshing that controller after saving

I have a table view inside a view controller. When i want to add something to my tableview i press the add-button and i go to a detail view controller where i have a save button. Now I want to go back to my tableviewcontroller and refresh it after pressing my save button.
I'm sending my data to an external database and not in the core data.
Since you are using modalViewController, after you tap Save button, you may dismiss it and to refresh the data, you will need to Use NSNotification in this case.
Try the following code:-
Inside the view controller that contains the tableview,
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(tableReload) name:#"tableReload" object:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"tableReload" object:nil];
}
-(void)tableReload{
[self.tableView reloadData];
}
In the second view controller, IBAction for the save button
[[NSNotificationCenter defaultCenter]postNotificationName:#"tableReload" object:nil];
[self dismissViewControllerAnimated:YES completion:nil];

NSNotification is being called multiple times from UITabBarController

I have a UITabBarController, which has 4 tabs. Each one of those tabs is a separate UIViewController. I have objects on each one of those 4 VC's that use NSNotification's to perform actions upon the press of a certain object. The 4 VC's all respond to the notification in the same way because it is a similar object on each page. When this object is pressed it presents a view onto the current view controller. The problem is that if I move to any of the other 3 tabs now that view is also on their VC. That is because the notification is being responded to on all 4 tabs when it is pressed on any of the VC's. I am needing it to only respond to the VC that the user is currently on and not any of the others that are in the tab bar.
Is there a way to get this to work properly? Maybe a threshold where you can set how many times the notification can perform its selector after being called? That way I could set it to 1 and at any given time if that notification is called the selector can only be called 1 time.
The type of object implementation that I'm using requires me to use NSNotification's so there is no way to change how I interact.
edit:
This viewDidLoad method is on the top level VC for the 4 VC's in my tab bar. All 4 of them either use this directly or inherit from it.
- (void) viewDidLoad
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSelectItemFromCollectionView:) name:#"didSelectItemFromCollectionView" object:nil];
}
Action Handler:
- (void) didSelectItemFromCollectionView:(NSNotification *)notification
{
NSDictionary *cellData = [notification object];
if (cellData)
{
NewVC *pushToVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PushToVC"];
[self.navigationController pushViewController:pushToVC animated:YES];
}
}
Each of the 4 VC's is a UITableViewController and have cells with an object that can be pressed. This NSNotificationCenter action is what allows the operation to work.
You must have implemented the NSNotificationCenter's -addObserver:selector:name:object: method in the -viewDidLoad of every viewController
Example:
- (void)viewDidLoad
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
Instead of having this in -viewDidLoad, move it within -viewWillAppear and implement removeObserver:name:object: in -viewWillDisappear.
This way, only the viewController that is currently on will respond to the notification.
Example:
- (void)viewWillAppear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] removeObserver:self
name:#"TestNotification"
object:nil];
}
- (void)doSomething:(NSNotification *)userInfo
{
//...
//if you push a viewController then the following is all you need
[self.navigationController pushViewController:vcSomething
animated:YES];
//however.... if you're instead presenting a viewController modally then
//you should implement "-removeObserver:name:object: in this method as well
//[NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
//[self presentViewController:vcSomething
// animated:YES
// completion:nil];
//OR... in the completion parameter as:
//[self presentViewController:vcSomething
// animated:YES
// completion:^{
// [NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
// }];
}
EDIT:
You (#Jonathan) commented:
I really appreciate your answer and it has helped me out a lot! I
actually ran into one more scenario where this issue occur's and I'm
not sure how to figure it out. Right now I have a VC that presents
another VC modally. Each one has observers for the same
NSNotification. Everything performs perfectly well when I'm in the
modally presented VC, but once I dismiss that VC and return to the
underlying one I have the same issue where the notification is being
called multiple times. Do you have an idea for a solution in this
case?
Now... regarding this...
FIRSTLY... NOTE:
Multiple -addObserver:selector:name:object: will register the specified notification multiple times (means... same notification being registered for N times will call invoke the target selector N times)
Presenting a ViewController (call it Child) from, say, Parent viewController will NOT invoke the -viewWillDisappear: of the Parent
where as...
Dismissing the Child viewController will still invoke -viewWillAppear: of the Parent
This creates an imbalance in the logic and if not handled (as per the commented lines in the code example of the doSomething method above), it results in the Parent registering for the notification multiple times (as it's -viewWillAppear: method is called more often than not -viewWillDisappear:)
also see: similar question
it should be called only once so that it never gets called again
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodName:) name:#"name" object:nil];
});

Update table view data after segue from detail

I have got a problem. I need to update data of master view data on segue from detail view. I've tried to implement prepareForSegue method, but it's not called on tapping back button.
What's the way i can do that?
Prepare for segue doesn't get called when popping a view controller from the navigation stack. I'd suggest calling [tableView reloadData] either in the detail view's viewWillDisappear method, or in the master view's viewWillAppear method. Alternatively, if you want to reload your table view after making a change, you can try using notifications. Something like the following in your master view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refresh:) name:#"RefreshMasterNotification" object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)refresh:(NSNotification *)notification
{
[tableView reloadData];
}
And something like the following whenever you make a change in your detail view controller:
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshMasterNotification"];

MVC consistency, present a viewController from the model

I have a viewController I've built in storyboard. I also have a NSObject Subclass which acts as my model, which sends and listens for API requests and responses. When a method fires in my model, I want to present a modal View of my viewController from whatever view happens to be visible at the time.
An example would be if my API hears "show this view" I want to show viewController regardless of what view is being shown.
Conceptually, how does one do this?
EDIT: I don't know which view controller will be showing when I want to present my modal viewController. Also, I need to pass params from my model to the modalVC when it's presented.
I would send a notification from the model telling "someone" that some view needs be displayed.
NSDictionary *userInfo = #{ #"TheViewKey": viewToDisplay];
[[NSNoticationCenter defaultCenter] postNotificationName:#"NotificationThatThisViewNeedsToBeDisplayed" object:self userInfo:userInfo];
And then on the delegate (or the active view controller) would register to this notification and handle the display.
// self is the delegate and/or the view controller that will receive the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleViewToDisplay:) name:#"NotificationThatThisViewNeedsToBeDisplayed" object:nil];
If you put in the view controller remember to remove self from the observers when the view is not visible:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationThatThisViewNeedsToBeDisplayed"];
This way your model is decoupled from the presentation.
You have the current viewController (any viewController subclass) present the new view using:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
EDIT: To find the top view controller, you ask the UITabBarController for the selectedViewController (if you use a tabBarController) to get the 'seed', or start with the window.rootViewController.
Once you are past any tabBarControllers, then you should only have UIViewController subclasses and UINavigationControllers. You can use a loop like this:
- (UIViewController *)frontmostController:(UIViewController *)seed
{
UIViewController *ret;
if([seed isKindOfClass:[UINavigationController class]]) {
ret = [(UINavigationController *)seed topViewController];
} else
if([seed isKindOfClass:[UIViewController class]]) {
ret = seed.presentedViewController;
}
return ret ? [self frontmostController:ret] : seed;
}

Resources