I am using Split View Controller. For master I have Navigation Controller with Table View Controller as a root view. For details I have Navigation Controller with Custom View Controller as a root view. From master I am selecting tableView row and displaying row details in details view. From details view i can edit details of this row using another View Controller with modal segue. The question is: How can I refresh tableView (master) after saving changes in details editing view (modal). When I tap save button the -(IBAction) unwindEditRow:(UIStoryboardSegue *)segue (of details) is triggered.
You have a lot of ways to send messages to ViewController when particular event has happened.
The first method: Use delegation pattern. What is delegate?
Delegation is a clean and simple way of connecting objects and helping them to communicate with other. In other words, delegation is a dating service for Objective-C objects. :) There are some useful links: Basic concepts in Objective-C, Writing your own custom delegate
Here is the way of declaring your new protocol:
#protocol DetailViewControllerDelegate <NSObject>
-(void)itemHasBeenChanged:(id)edittedObject;
#end
In your DetailViewController declare your future delegate:
#property (weak,nonatomic) MasterViewController <DetailViewControllerDelegate> *delegate;
Implement itemHasBeenChanged: method in MasterViewController.m:
-(void)itemHasBeenChanged:(id)edittedObject{
//editting logic goes here
}
Tell our class to implement the DetailViewControllerDelegate protocol so that it knows which functions are available to it by line:
#interface MasterViewController : UITableViewController <DetailViewControllerDelegate>
After all these steps, you can call method in your DetailViewController whenever you want by:
[self.delegate itemHasBeenChanged:yourObject];
Here is my example code on github
Second way
We can use NSNotificationCenter as an alternative to custom protocols. Making custom protocols and registering methods is hard to do in a big project and NSNotificationCenter can free us from that burden. The main approach used with NSNotificationCenter is that any object can send a notification to the notification center and at the same time any other object can listen for notifications on that center.
Related
Yesterday I posted this question so all of the code can be found there for the structure of my problem.
The Problem... This Time
I have come from other languages to OBJ-C and some of the OOP structures are making me cringe a little bit (I don't like packing every possible function into a single UIViewController as some seem to do). I was originally going to make a full page UITableViewController with an embedded NavigationController however the use cases of this project would not allow me to use the default navbar. So I had to put in my own navbar and use a regular UITableView (resized to be pretty much full screen) instead of the simpler option, the UITableViewController... (I am aware this all could be solved by using it, but I cannot)
Instead I have a regular UIViewController with a property containing my own custom TasksTableView.h subclass. The subclass extends UITableView as seen in the link I posted above.
The actual problem is that I cannot seem to Segue or change views from inside of this UITableView because every function which does so, seems to need to come from the UIViewController class.
I Have Tried
Calling a manual segue in the didSelectRowAtIndexPath method of my UITableView subclass.
[self performSegueWithIdentifier:#"profile" sender:sender];
Which produces an obvious error telling me that performSegueWithIdentifier does not exist on this class, which it doesn't so thats fine. Obviously it belongs to the UIViewController class that instantiated my UITableView sub class...
I have tried importing the view controller that actually renders and holds the property of my table view subclass itself and trying to push the view to the stack.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *target = [storyboard instantiateViewControllerWithIdentifier:#"SingleTaskViewController"];
AllTasksViewController *allTasksView = [[AllTasksViewController alloc] init];
if(target) {
[target setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[allTasksView presentViewController:target animated:YES completion:nil];
}
}
This gives me the error Warning: Attempt to present <SingleTaskViewController: 0x7fa15d5359f0> on <AllTasksViewController: 0x7fa15d5363c0> whose view is not in the window hierarchy!
... Even though it must be in the heirarchy because it is the view that contains and instantiated this UITableView.
I have also tried manually invoking the didSelectRowAtIndexPath from the UIViewController that holds the UITableView sub class but again it was the same kinda thing. It invoked, however obviously since I had to pass in the index, it is me picking it instead of the table telling me what was actually selected.
What I Want
I would really like to keep the UITableView sub class seperate from the UIViewController and not bring the delegate methods and protocols to the view controller. I would prefer to keep the logic separated. All I need is a way to segue or transition to the Single Task View in question and send some data with it about what was pressed.
There are good tutorials out there, but the basic idea is...
Main "ViewController" class - contains a Table View, and a "manual" Segue to a "Profile" View controller
Separate Datasource and Delegate classes for the table view
Custom Protocol / Delegate to send the "didSelectRow" action
When the main vc loads, it creates instances of the Datasource and Delegate classes, and assigns them to the table view.
It also "conforms to" a custom Protocol in the Delegate class. This allows the Delegate class to "call back" to the main vc when a row is tapped.
I put together a very simple example demonstrating this approach that can be seen here: https://github.com/DonMag/OCTableViewExample
Is there a way to accomplish this? Like let's say a framework perhaps?
All I need is something like:
aViewController appeared
bTableViewController appeared
cViewController appeared
Like I added a code in each viewDidLoad, but without adding that code. I know that there is a visibleViewController if I used navigation embed but I don't and I can't.
The framework I'm working on is based on no baseline, so I can not assume anything, like I'll past a code in a project and when the user compiles the code, I will get the views.
The purpose is I'm creating an analytic tool without being have to add individual code in each view controllers's viewDidLoad method.
First of all, there's no such thing as a "visible view controller" technically speaking, despite the annoyingly named method of navigation controllers. Instead, we should be talking about visible views. There's nothing visible about the controller.
But what you really want to know is what view controller is active and controlling visible views. With a navigation controller, it's the top of the nav-stack, with a tab bar controller, it's the selected view controller, etc.
By implementing ONLY code in app-delegate, I don't know of any way to accomplish what you're trying to accomplish.
Instead, what would be your best option would be to provide a framework that included a subclass of UIViewController and UIAppDelegate.
Set the code in these to do all the analytical work necessary. Provide this framework as a whole and inform your users that if they want to make use of the analytics you've provide, they'll need to subclass your view controller instead of UIViewController, etc.
In your custom classes, just override viewDidAppear: to send a notification and viewDidDisappear: to send a notification. And the appdelegate just manages an array that keeps track of which is on top, adding when the viewDidAppear: notification fires, and removing when the viewDidDisappear: notification fires.
And to be sure your viewDidAppear:/viewDidDisappear: isn't overridden, be sure to make use of this: When a subclass overrides a method, how can we ensure at compile time that the superclass's method implementation is called?
So in your view controller base class, it'd be as simple as posting a notification:
// AnalyticViewController.h
#interface AnalyticViewController : UIViewController
- (void)viewDidAppear NS_REQUIRES_SUPER;
- (void)viewDidDisappear NS_REQUIRES_SUPER;
#end
// AnalyticViewController.m
#implementation AnalyticViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AnalyticVCAppeared"
object:self
userInfo:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AnalyticVCDisappeared"
object:self
userInfo:nil];
}
#end
And register for these in the App delegate in application:didFinishLaunchingWithOptions:. You'll want an mutable array to keep track of the stack of view controllers. And you'll want separate methods for each notification.
Given a property #property (nonatomic,strong) NSMutableArray *analyzedViewControllers;, the methods that respond to the notifications should look like this:
- (void)handleViewAppear:(NSNotification *)aNotification {
if (!self.analyzedViewControllers) {
self.analyzedViewControllers = [NSMutableArray array];
}
[self.analyzedViewControllers addObject:[aNotification object]];
}
- (void)handleViewDisappeared:(NSNotification *)aNotification {
[self.analyzedViewControllers removeObject:[aNotification object]];
}
And the top-most view controller will always be at [self.analyzedViewControllers lastObject].
If you need to send more information about the view controller, you can using the userInfo argument when posting the notification. You could even put the entire notification into the array, so you have the view controller reference and the user info reference.
What can also be important to note however is that just because one view controller has appeared doesn't inherently mean another has disappeared. View controllers don't necessarily take up the entire screen, and although there's eventually a practical limit to how many view controllers have visible views on screen, it's a quite high limit.
ADDENDUM: To add some clarity... in the AnalyticViewController class I'm recommending, we have this line:
#interface AnalyticViewController : UIViewController
A line similar to this exists in every one of your view controller's header files. What I'm recommending is that all your other view controllers subclass this one, so instead of:
#interface FooBarViewController : UIViewController
You'd instead have this:
#import AnalyticViewController.h
#interface FooBarViewController : AnalyticViewController
And the code in the viewDidAppear: and viewDidDisappear: of AnalyticViewController is now automatically in FooBarViewController unless you ignore the NS_REQUIRES_SUPER warning and override the viewDidAppear: or viewDidDisappear: methods.
It will be a little bit of effort to change over existing view controllers, but for any future view controllers that are not yet created, it's as simple as choosing to subclass AnalyticViewController rather than UIViewController when you create the file.
For creating a analytical framework i would say you should write a kinda protocol of a rootviewController and viewController class and that delegate methods can be implemented in app delegate for knowing and handling your purpose.
I have a view that I want to reuse in different situations. It is a user view that, when touched, will have the viewcontroller push a user detail viewcontroller.
So basically I have a view that can any number of superviews until the viewcontroller. I want that view to be able to notify whatever viewcontroller that is currently being displayed to push the user detail view.
Is there a way besides using NSNotificationCenter to do this? Is NSNotificationCenter my best option? I've tried to put in a protocol/delegate, but that isn't working out for me.
Thanks!
------------------------Response to a comment----------------
I would like to have it so it is dynamic. That is partially my problem. I will use this view throughout my code and when I make updates/changes, I don't want to have to change the actual user view to make things work
An example would be adding this user view on the following hierarchy: viewcontroller->tableview->tableviewcell->userview. But then I'd also like to add it like this: viewcontroller->userview.
navigationController.topViewController may be helpful in this case. Or if your app is using a single navigation stack, you could handle this notification in the appDelegate
#interface AppDelegate
#property (nonatomic, strong) UINavigationController *nav;
...
[nav pushViewController:userVC animated:YES];
I think it does make sense to use an NSNotification in this case. Per MVC, the UIView handling the touch event should not need to know much about the View Controller hierarchy it lives in. Notifications handle that issue.
I am thinking that I will subclass a UINavigationController and register for my NSNotification there, then i won't have to worry about registering on each UIViewController in my app. I'll leave this answer here for a bit without checking it as the answer to see what kind of side effects this might have.
I have a relatively simple application. I have a UINavigationController and always have my mainViewController pushed on it. Occasionally I'll push on a settings and sub-settings controller. In my sub-settings controller, the user can make modifications to ivars in my mainViewController. Right now I have these ivars declared as properties and am setting them directly. I am using self.navigationController.viewControllers[0] to get a reference to the main controller then setting the properties. Is it better to use NSNotificationCenter?
It is not a very good design to have your settings controllers have any knowledge of your primary view controller. What happens when you add more functionality to your app in the future and more screens in the app need to deal with changes in any settings?
It is a far better design to separate the behavior. Your settings view controllers should update a "settings model" of some sort. The class representing this model should then be able to broadcast any changes. Using NSNotificationCenter for this is a good approach.
Now any class that might care about changes in settings can register for the appropriate notifications and act accordingly when there is a change.
This way you can have multiple view controllers or other classes that respond to settings changes and nothing in the settings code needs to care about any specific view controllers or how many there are.
What you are doing is fine since you can easily get a references to your main view controller from your settings view controllers. It's really just a matter of preference.
The real purpose of NSNotificationCenter is when multiple objects need to be notified of an event or where it's difficult to get a reference to the object you want to modify. NSNotificationCenter can also make your code cleaner and easier to modify.
For instance, if you change the design of your app in the future such as moving the settings view controllers to tabs rather than pushing them onto a navigation controller you might find it more difficult to modify your main view controller directly from them.
It would be more appropriate to use NSNotificationCenter when you have disparate components in your app that would logically have no understanding of each other's API. However, where you are displaying a view for a sub-settings controller, it would be easy to have a settings object that you could just pass from the mainViewController to the subSettingsViewController. In your sub-settings view controller, in the -viewWillDisappear: memthod, ensure that all of the values have been saved to your settings object. Finally, in your main view controller's -viewWillAppear: method, you would just update the UI of the main view with the values in your settings object.
Here's some code to illustrate this:
MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) MySettings *settings;
#property (nonatomic, strong) SubSettingsViewController *subSettingsViewController;
#end
#implementation MainViewController
- (IBAction) showSubSettings: (id) sender {
[self.subSettingsViewController setSettings: self.settings];
// Present 'subSettingsViewController'
}
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear: animated];
// Set values from settings object for various text fields, UI controls, etc.
}
SubSettingsViewController.h
#interface SubSettingsViewController ()
#property MySettings *settings;
#end
SubSettingsViewController.m
#implementation SubSettingsViewController
- (void) viewWillDisappear: (BOOL) animated {
[super viewWillDisappear: animated];
// Set values for the appropriate properties in 'self.settings'
[self.settings setValue1: self.textField1.text];
}
I have a TableView which describes a book with sections which represents the chapters and rows representing the verses.
A the top of this TableView I have a button in a navigation bar to allow "navigation".
The goal of this navigation button is to allow the user to easily jump to a given chapter/verse without scrolling manually (which can be very long).
When the button is pressed a tableview controller is called displaying all the available chapters of the book and when a chapter is selected another table view is called displaying a list of the available verses in the current chapter. Finally when the line is chosen the tablew view displaying the book should scroll to the given index/row.
So the idea : from the tableview representing the book I call the chapters view as modal and the verses as a push over the chapters view.
My problem is that I don't get the point of managing the delegate and dismissing from the 2nd modal view.
With 1 modal view I do things like that.
In the displayed VC (View Controller) I added the protocol and the delegate
#protocol ChapitresTableViewControllerDelegate <NSObject>
- (void)didDismissPresentedViewController;
#end
#interface ChapitresTableViewController : UITableViewController
#property (nonatomic, weak) id <ChapitresTableViewControllerDelegate> delegate;
#end
I have in the didSelectRow
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didDismissPresentedViewController];
}
in the displaying VC I add the following line
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ChapitresTableViewController *chapitresTableViewController = segue.destinationViewController;
chapitresTableViewController.delegate = self;
}
and of course
-(void)didDismissPresentedViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
this would just work fine if I wanted to close after the first modal VC, but what I want is to have the second one being dismissed after I click in the second. Ok I can write the protocol and stuff in the second modal VC but how do I manage to have the delegate being send to the second VC.
Hope my question is clear enough it is not so easy to explain it.
Anyone understand me and can help me ?
NOTE : I know for now I don't pass any parameters back to the delegate, which I will do later to do the scroll. For now I just want to be able to close the second view, then I will add the required stuff to pass the parameters back to the delegate
I'm sure you can do this, but rather than modal view controllers with a navigation bar, wouldn't it be easier to use a navigation controller? Then you can use popToViewController to go back as many levels as you want to a particular view controller. You can either pass the UIViewController* of the various controllers you might want to pop to, or do so programmatically: e.g. How to pop back to specify viewController from navigationController(viewControllers/stack)?
In this scenario previous views controllers are retained. The ones you pop off are released (just like the modal ones you dismiss are released), but the ones that you pushed from are retained (just like the ones you presented from in a modal world are retained).
If the book is large, though, you'll have to be sensitive to memory usage. Thus, you will probably want to handle didReceiveMemoryWarning to release the model data for the previous views in either your modal sequence or push sequence, in which case, on viewDidAppear, you'll want to see if your app had to release the memory in response to didReceiveMemoryWarning and reload it in that case. But that's the desired behavior, either way, gracefully release the pages if needed (and reload them when the particular view reappears), but keep it in memory if you can.
Finally, you might also want to contemplate using UIPageViewController. Given what you've described, I'd like consider UIPageViewController first, UINavigationController and push segues second, and the use of modal segues third.