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];
}
Related
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 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.
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've got a view that contains a number of child views and I want the parent view to be able to respond to events that happen to its children, without a lot of manual relaying of events from the children.
Specifically, suppose I want the parent to be notified any time the user taps on any of its subviews, but also without interfering with the default action of the subview. For instance, if you tap on a UITextView, I want the text view to become the first responder as usual, I just want to know about it.
How can I do this?
Not sure if by "without a lot of manual relaying of events" you mean "without NSNotificationCenter", if not you could obv use:
[[NSNotificationCenter defaultCenter] postNotificationName:#"subViewTouched" object:subView.tag];
// Where you set up tag as each subview's unique id
Again, not sure if you're looking to specifically not use NSNotificationCenter.
Definitely use delegation, or setup your UI elements as public properties and wire the events in your view controller. Delegation is the better route. In my opinion, NSNotificationCenter is hard to debug and is error prone. Use delegation.
.h
#protocol MyViewDelegate;
#interface MyView : UIView
#property(weak, nonatomic) id<MyViewDelegate> delegate;
#end
#protocol MyViewDelegate <NSObject>
-(void)myViewButtonClicked:(MyView*)myView;
#end
.m
-(void)onButtonClick:(id)sender {
if(self.delegate) {
[self.delegate myViewButtonClicked:self];
}
}
handle the delegate method in your view controller
-(void)myViewButtonClicked:(MyView*)myView {
//handle whatever
}
Got a question, I'm trying to return to a previous view and share some data over to the frame I'm returning to. The data will be date and time and I would like to send this to a textField.
For example I'm calling the date *returneddate and the textField I'm calling *dateTime. The views are call *PickDateTime and SubmitEventsP2.
If you need more information just ask me and I'll add it if I can to make it easier for you to help me.
I'm using Xcode 4.2.
Ok.. its pretty simple.. you should use a delegate... if i understood correctly, you are on a secondary view and when you return to the main view you wish to send the data from the second view back to the main view, right?
so, in your second view, in the .h file, on top of interface, you will declare the delegate with:
#class nameOfTheViewController;
#protocol nameOfTheViewControllerDelegate <NSObject>
-(void)methodNameOfDelegateReturning:(NSString *)string otherString:(NSString *)string2;
#end
And in your interface, still in the .h, you will create a reference of this delegate like:
#property(nonatomic, weak) id <nameOfTheViewControllerDelegate> delegate;
after that, in your .m of nameOfTheViewController you will do:
#synthesize delegate = _delegate;
After you created you delegate in the nameOfTheViewController file, you will call the delegate method you just created exactly where and when you want to return to the previous view, filling it with the parameters you want to pass back... and of course, in your mainViewController, right in your didPrepareForSegue method, you will create a instance of the nameOfTheViewController class and set its delegate proeprty to self... for this to be possible, in your mainViewController .h you must conform to the nameOfTheViewControllerDelegate protocol.