I have a superview and I add a subview to make a selection. In the superview (main view) I do the following:
[self.view addSubview:cityViewController.view];
In the cityView, when I have done what I need to do, I just do
self.view removeFromSuperView.
The question is, from within the superview, how can I tell when the subview has removed itself.
There's a few ways, but honestly since the current view controller (let's call it main) is just adding the cityViewController's view, keep the handling of adding/removing the views to the current view controller, and just have the main controller call [cityViewController.view removeFromSuperView]
This way you can execute whatever code you want when it receives this notification (be it a method that fires or a UINotification).
-- edit for sample UINotification code --
main.m
...
//Define cityViewController as an iVar and alloc/init it
[[UINotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishView:) name:#"DidFinishView" object:nil];
[self.view addSubview:cityViewController.view];
...
-(void) didFinishView:(NSNotification *)notification {
[cityViewController.view removeFromSuperView];
}
CityViewController.m
-(IBAction) doneButtonClick:(id) sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidFinishView" object:nil];
}
The quick answer is your view should not be removing itself. It's better practice for a view to communicate user interactions to a relevant controller through an interobject communication mechanism. The most common methods are direct messaging, protocols and notifications. The iOS framework uses all of these and there are great docs explaining them. Here's a brief summary:
Direct messaging. Use this when an object needs to communicate with a specific object of a known type. For example, if MyView is always contained in MyViewController and needs to send messages to it you can add a property to the MyView class that keeps a pointer to the specific MyViewController object. You can then send a message from myView to it's myViewController via [myView.myViewController userDidTapSaveButton] or whatever.
Protocols. A protocol defines a contract between objects that don't know anything about each other other than that they abide by the contract. For example, UITableView knows that it's delegate conforms to the UITableViewDelegate protocol and it can send the required protocol messages to it's delegate. Any object can conform to the UITableViewDelegate protocol.
Notifications. Notifications allows an object to post notifications through a central mechanism (NSNotificationCenter) that other objects can observe and respond to. Notifications are useful when the object posting the notification doesn't know or care what objects are observing it's notifications.
I'd read the relevant docs and other Q&A on SO about these methods. I'd also study up a bit on the MVC (Model/View/Controller) design pattern so you get more comfortable knowing where to put app logic. Generally, a view should only be responsible for it's display (based on properties set by it's controller), observing/responding to user actions, and notifying it's controller for relevant actions.
Related
I'm creating an iOS app with 4 tabs (4 view controllers) that gets CoreLocation updates and displays the location along with some other data in various representations (table view, map view, etc)
I only want to use one CoreLocationManager, so I can only have one delegate, but 4 view controllers need to know about the updates so that the visible view can be updated
What is the best way to let my view controllers know that there has been a location update?
The simplest is to post a notification rather than using a delegate. Delegates are 1:1 where as notifications are 1:many. Problem is you still need at least one delegate which will post the notifications.
If you want, you can create an NSProxy object which you can set as the location manager delegate and which internally holds a list of other delegates and forwards all of the received method calls to all of the internally managed delegates.
The BEST method is probably to simply use the notification center. Rather than a #protocol, put a NSString * const you're using for the notification:
NSString * const kMyNotificationString #"MyNotificationString"
Now, when the would-be delegates need to respond, it's as simple as:
[[NSNotificationCenter defaultCenter] postNotificationName:kMyNotificationString
object:nil];
Any object that needs to respond to this notification can #import this header file (so it gets the NSString * const-ed notification name and then and start listening to the notification as such:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMethod)
name:kMyNotificationString
object:nil];
Just don't forget to remove observers when (or before) you dealloc.
Alternatively, there's this approach... but it's not really that great, and I'd actually recommend against it.
Rather than a single delegate property, you could have a NSArray property that handles the "delegates" (make sure you have an "addDelegate" method that only adds weak references to the array). Then, when you need all of these objects to respond to a change:
for (id<MyProtocol> object in self.arrayOfDelegates) {
if ([object respondsToSelector:#selector(myMethod)]) {
[object myMethod];
}
}
The easiest way is implement one delegate that forwards messages as notifications, this way any number of objects can subscribe as observers for those notifications.
I am wondering if there is a good, general paradigm for setting up the refresh action in an arbitrarily deep UITableView. I'm speaking specifically of the refreshControl property of UITableViewController.
One layer is pretty easy and obvious since you just refresh your model and then call [self.tableView reloadData], but even two layers things start conflicting with my notions of good OO programming.
So if the user pulls the table view down to refresh from an arbitrarily deep table, it strikes me that it does absolutely no good to just reload the data from the parent view (which doing, it seems to me, isn't a good OO setup since a child controller isn't supposed to be able to talk directly to the parent). The refresh call needs to be passed back up through the UITableViewControllers until either the top, or some point that would conceivably be the deepest point where the model might have changed, and then push the new, refreshed, data back down through the child UITableViewControllers until it reaches the controller which originated the refresh (checking that that path even still exists at each jump).
The only way I'm coming up with doing this is to have a property in each controller called parentView which is set during prepareForSegue and a method called something like dataForChildView.
-(IBAction)refreshTableView {
[self.refreshControl beginRefreshing];
//self.parentView would be nil at the topmost TVC
if (self.parentView) {
[self.parentView refreshTableView];
self.data = [self.parentView dataForChildView];
}
//refresh data
[self.tableView reloadData];
[self.refreshControl endRefreshing];
}
But this seems to me to violate the basic rules of nested controllers, for the reasons I state above.
I haven't yet learned about NSNotifications, but this strikes me as a possible approach. However, since I'm finding nothing online about this question, I wanted to ask before I commit a bunch of time to figuring out if NSNotifications will let me do this. Or maybe I'm missing some even simpler approach.
Edit: the assignment that this is for is set up such that only the topmost UITableViewController has access to data from the model; all lower controllers are passed their data in prepareForSegue. I am starting to think that perhaps this is the problem; there needs to be a model that can be accessed from any level down in the UITableView. Would that be correct?
You could for example refresh your model (which can be passed along to the view controllers) and reload the table views on viewWillAppear.
Also the way with notifications you mentioned will work.
Register for notifications in your viewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(selectorThatReloadsTheTableView:)
name:#"NameOfYourNotification"
object:nil];
Don't forget to remote your view controller from the notification center by calling
[[NSNotificationCenter defaultCenter] removeObserver:self];
Now all you need to do is post the notification once the user pulls-to-refresh:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification"
object:nil];
I'm still getting the hang of IOS delegation, so I hope this question makes sense as I explain what I want to do...
What I want to do is download some JSON Data on a background thread as soon as my app first runs. The data will then be parsed and then update a global variable. Once that happens I want all previously pushed view controllers to update their content based on the data that has been downloaded and parsed.
My proposed way of solving this problem would be to have either my app delegate or my custom Navigation Controller subclass be a delegate for a custom JSON object that will parse the data. The delegate will run a protocol method that updates a global variable once the parsing is complete.
Now once this variable has been set, it will be available to any view controller that is pushed on to the navigation stack. I also want to update the view controllers that have been previously pushed so that their content can be updated.
I know I can make the top view controller an active delegate that will run a protocol but what about the previously pushed view controllers? Is this even possible or is there another way to make previously pushed view controller update their content?
In this case you don't really want to use a delegate. You want several objects to listen to a specific event, so use NSNotificationCenter instead.
When your parser finished parsing the JSON do the following:
[[NSNotificationCenter defaultCenter] postNotificationName: #"FinishedDataParsing" object:self userInfo:nil;
This way you also won't need a global variable. You could either make your parsed data accessable in your parser object or use the userInfo dictionary to pass some information to the notification receiver.
Everywhere you want to do something when your parsing finishes you first have to register as an observer (you could do that in viewDidLoad):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dataParsed:)
name:#"FinishedDataParsing"
object:nil];
And obviously you have to implement your callback method to do what ever you want to do with your parsed data.
- (void)dataParsed:(NSNotification *)notification {
// Do this to access the user info.
NSDictionary *userInfo = notification.userInfo;
// Or access your data parser object.
DataParser *parser = (DataParser *)notification.object;
}
Also you should deregister as observer when you no longer need to get notified (e.g. in dealloc)
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Ok so this is the situation I have.
I have created new project using master detail template and in MasterViewController I have table and cell which was displaying title only. I modified cell to be custom and added two text-fields. My app requirement is to plot graph real-time as soon user enters point in textfield. Graph is displayed in detail view and that part works fine (Graph plotting).
Now my problem is how to transfer data from the class which is not uiview controller. MasterViewController is subclass of UITableViewController but to create outlets for the textfields I have to have subclass of UITableViewCell. So I created new class CellData (subclass of UITableViewCell) and I can get values from textfields real-time using IBAction didFinishEditing and IBOutlets for the textfields and data is automatically added to array.
Now how should I push this array to MasterViewController and then further to class GraphDraw which will actually draw graph. What I did is to create global class which I suspect is not ideal solution. Should I create three way protocol and delegate solution or maybe model class as someone suggested?
So far all the protocol and delegate tutorials were focused on viewControllers and I have no problems with that but they don't quite work for my situation. And with model class which is subclass of NSObject I cannot directly get cell data. Now I apologise if question is long but I am beginner and I am trying to figure out best way to solve these problems. Any help is appreciated.
Delegation is the way to go here. This setup though is a little confusing and is probably why the resources out there are not exactly what you are looking for.
First you will need a delegate that can send messages from your UITableViewCell subclass to the master UITableViewController. Take the following for example:
CellData
#protocol JDPlotCellDelegate <NSObject>
- (void)didFinishEditingWithData:(id)someData;
#end
Now when the UITextField sends its didFinishEditing delegate message:
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.delegate didFinishEditingWithData:textField.text];
}
Simple so far right?
Next, your master tableview controller needs to implement the delegate (and make sure the cells have the delegate property hooked up!)
MasterViewController
- (void)didFinishEditingWithData:(id)someData {
[self.detailViewController plotSomeData:someData];
}
Then since the master has a reference to the details view controller it can be as simple as declaring a public method - (void)plotSomeData:(id)someData; on the detail view controller that can take the data and plot it.
Hope this helps!
if you are not willing to use delegates its ok, I mean you could use NSNotifications to tell other listeners in the app that some event has occurred and you could also piggy back some data with the notifications.
In the reciever of the notfication -viewDidLoad perhaps add this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(masterEventOccurred:) name:#"MasterEventNotfication" object:nil];
Add this function in the listener class
-(void)masterEventOccurred:(NSNotification *)notification
{
//notification happened
NSLog(#"object sent: %#",notification.object);
}
In dealloc of listener class remove notification observer
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Now everything is set up we have to fire the event, add the below lines wherever you want in the master class(according to you, may be in textField delegate method):
[[NSNotificationCenter defaultCenter] postNotificationName:#"MasterEventNotfication" object:yourTextField.text];
I have a viewController, in which I have a view in which I draw like in Paint by tapping. In that controller I have a method to clear screen -
- (void) clearS
{
[(TouchTrackerView *)self.view clear];
}
Now, as I don't want to occupy my screen with buttons, I have button in another screen that resets application to starting position, and I want it to clear screen. That button belongs to different view controller, and among other things I want it to call my drawing view controller and ask it to wipe screen clear. So I have setup notifications, like this in draw view contoller:
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(clearS) name:#"clearScreen" object: nil];
And like this in my view controller from where I click a button:
[[NSNotificationCenter defaultCenter] postNotificationName:#"clearScreen" object: nil];
As I understand, when the button gets clicked, notification gets posted, and method is called. And it crashes.
[(TouchTrackerView *)self.view clear]
As I understand the problem is that at the moment of method invocation the "self" variable is my non-drawing view controller, and it tries to perform clear method on view of itself, which it lacks and crashes.
How do I send the method to correct view controller?
Is there a flaw in my thinking? Maybe this can be approached in a better way?
EDIT:
I have found the problem,
[(TouchTrackerView *)self.view clear]
calls view and it is UIScrollView, and it does not support clear method. So I have made a property containing the correct view, and changed it to
[(TouchTrackerView *)self.correctView clear]
and it works like a charm.
I have chosen notification because it is only two lines of code, and I am beginner and it is hard for me to wrap my head around delegates, so I will leave this as it is, especially that it works.
At the philosophical level, while I'm sympathetic to the observations from others that you could replace the notification with a delegate pattern (or, better, delegate protocol pattern), it strikes me that this is not an appropriate example of having two controllers communicating directly to each other at all. You probably should be employing a MVC model, where the editing view controller, A, is designed for the editing of a drawing (and thus updates the model and coordinates the view) and the reset view controller, B, should just update/reset the model. If A needs to be informed of model changes, then apply a delegate-protocol pattern there, between the model and controller A. But I don't think B should be communicating with A at all.
At a practical level, there's absolutely no reason why notifications shouldn't work just fine. Unfortunately, you haven't shared enough for us to answer the question. You should share the specifics of the error message and we might be able to help you more.
I think that notifications, which are in nature a one-to-many method of communication are not good to use here. A better approach would be to hook one as the delegate of the other -> once the button is pressed and the corresponding IBAction is invoked, the delegate (in your case, the view controller you use for drawing) should get a message and perform whatever it is it needs to do.
I would avoid notifications unless you are trying to broadcast out some info that multiple objects may be interested in.
A better approach might be to create a delegate protocol for the painting view controller --
So the PaintingViewControllerDelegate Protocol may have methods like so
(void) paintingViewControllerWillClear:(PaintingViewController*)paintingViewController;
(void) paintingViewControllerDidClear:(PaintingViewController*)paintingViewController;
Now the controller with the buttons becomes the delegate to the PaintingViewController and that object provides the methods of the PaintingViewControllerDelegate protocol as needed.