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];
Related
I have a question concerning iOS. I couldn't find an answer to my question online but maybe one of you know the answer.
Im working on an app which pulls data from an online database. A NSObject Class is called by a timer in the background of the app. I want it to tell one of my view controller to reload its tableview if it finds new data. Like it is supposed to check for data not caring in which view controller im in. But when I'm in the specific one with my table view it should reload the table view if it found new data.
I've tried to create an instance of the tableviewcontroller in my NSObject and call the reload table view function front there, but that doesn't work :/
I apologise for my poor explanation, I'm not a native speaker.
Thank you very much :)
You can use two approaches:
post a NSNotification in your class that gets more data and observes
this notification in your class that the TableView is initialized.
make a delegate approach between the two classes and fire a method when you have the new data.
I would go with the first option:
a. in your data loading class:
NSNotification *notification = [NSNotification notificationWithName:#"newDataFetched" object:anyObject];
[[NSNotificationCenter defaultCenter] postNotification:notification];
b. in your listener class:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(aMethodToReloadTheTableView:)
name:#"newDataFetched"
object:nil];
I'm very new to objective-c and programming in general, and just started building a tab-based app in xcode. I have three view controllers and a slider in each of the views. I want the sliders in the second and third view to copy the position (and therefore the value) of the slider in the first view - and vice versa. So that irrespective of which view the user is at, it looks as though there is only one slider throughout.
I’m pretty sure there must be a simpler way to go about this. I hope I’ve explained my issue adequately.
Any advice would be greatly appreciated.
There are a few solutions you can implement.
You can create one slider and have a reference to it in some type of singleton object like the AppDelegate and pass it around to the respective views adding as a subview. Since a view can only have one super view your are safe.
You can use NSNotifications to notify each of the sliders that a value has changed. NSNotifications allow you to pass in an NSDictionary call userInfo so you can pass values around.
My personal opinion is that number 1 is cleaner, NSNotifications are heavy and there might be a slight delay on when things actually get updated.
Add following target to your slider
- (IBAction)sliderValueChanged:(UISlider *)sender
{
// fire notification here
[[NSNotificationCenter defaultCenter] postNotificationName:#"SliderValueChanged" object:nil];
}
and in your other view controllers, you can add observer for "SliderValueChanged"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onSliderValueChanged:)
name:"SliderValueChanged" object:nil];
- (void)onSliderValueChanged:(NSNotification *)notification
{
// Adjust your respective view controller's slider here
}
Another approach is to store your UISlider value in NSUserDefaults when it changes. Now in every controllers ViewWillAppear method you can get that value from NSUserDefaults and set it for your UISlider. That way no matter which controller the user is looking at it will show the same value for them.
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];
}
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.
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.