iPhone UITableView trigger a parent method? - iphone

Okay, so I'm working on an iPhone app with a preferences-esque section where there's a base TableView, which has one section, and several rows of customized cells to hold a name and a value label. Clicking on a row brings up another View, which allows the user to pick from a list (another TableView), and select an item.
All of these TableViews are done programatically. The base TableView has a property that holds a Controller instance for each of the "pick from the list" Views. Each of the "pick from the list" views has a property called chosenValue that has the currently-selected option. I've gotten the UI to handle the didSelectRowAtIndexPath to update the chosenValue property and then "pop" the view (going back to the main TableView). But, even though the main TableView's cellForRowAtIndexPath method references the chosenValue property of the subview that's held in a property, the view doesn't update when an item is selected. In short, how can the sub-view trigger a reloadData on the parent object, after it "pops" and unloads?

You could realize this using the notifications or delegation, for example.
Using notifications your first view controller has to register for a notification like this:
…
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToBeCalled:)
name:#"myNotification"
object:nil];
…
- (void)methodToBeCalled:(NSNotification *)notification {
[self.tableView reloadData];
// do something else
}
Then you can raise a notification in your second view controller like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil];
Using delegation you could implement a property for a delegate on the second view, set that to self before pushing the second view controller and call a method on the delegate when needed.

You should define a delegate for that.
In the child TableView define a protocol:
#protocol ChildViewControllerDelegate
- (void) somethingUpdated;
#end
Also define a property for a delegate implementing this protocol:
id <ChildViewControllerDelegate> delegate;
Then in Parent all you need to do is to define a method somethingUpdated, assign itself (parent) to a delegate property in Child and call this method in Child view when you need to update it. Implementation of somethingUpdated in Parent can be different based on what you're trying to accomplish.

flohei thanks so much for posting this!! I got it working with NSNotificationCenter! I have a child view calling a method in a parent view now! Awesome. #sha I tried your protocol way also but could not figure it out. Perhaps a more illaborate discription would help a noob like me. I have been reading that your protocol way is the better cause I guess less code? So i'll try to keep learning it. Thanks to you both!

Related

Reload table in model class

I have one view class. In this class I have table view. In my model class I make Asynchrous ASIHTTPRequest. And I want when the operation is successful to reload data in table view. Which is good way to do this. I consider add one UITableView property to model class and use reload it. Is this good approach?
Make your model post notification to NSNotificationCenter and your table view register for this notification and reloadData when it receives one.
Inside your model, when changes occur:
[[NSNotificationCenter defaultCenter] postNotificationName:#"uniqueNotificationName"
object:self];
Inside your table view controller, register for notification, for example in viewDidAppear:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCall:)
name:#"uniqueNotificationName"
object:nil];
And implement methodToCall:
- (void)methodToCall:(NSNotification *)notification {
[self.tableView reloadData];
}
When you are done with the table view, for example in viewWillDisappear: you need to unregister for notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self];
I think your model should inform te controller that is has updated (or other way around using KVO). The controller should then message the view to reload.
When you want to reload table view you can use one of this approaches:
If you want to reload all displayed cells then you can just call [tableView reloadData];. Notice, that only displayed cells will be reloaded. So if you have, for example, 10000 cells and only 10 of them are displayed, then only for 10 cells will be recalled method cellForRowAtIndexPath:.
If you want to reload particular cells then you should call following method of UITableView : - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation and provide in first operand list of cells to reload.
If you want to reload whole section (or number of sections) of UITableView then you should call - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation and provide in first operand list of sections to reload.
Hope, it will help you to choose appropriate variant.
use synchrous and gcd to load data.
when receive data call [self reload] on main queue

iOS: Notify other tabbed view controllers about a change in its dataset

I have a tab bar controller and inside it two controllers: a mapview controller and a tableview + NSFetcheddata controller. Both display info about a specific day from core data and have a button to display a day selector modally.
I have achieved having my controllers dataset changing when their modal view controller disappears through delegation but I would like the two controllers to update their data and not only the one who displayed the modal controller.
I thought about creating a protocol in both controllers and setting each other as its delegate but I would like to know if I'm doing right here.
Cheers,
Thierry
There are tons of different ways to do this. One way is to use NSNotificationCenter. Define your own custom notification name:
static NSString *const CSDataUpdatedNotification = #"CSDataUpdatedNotification";
Subscribe to this notification in both of your controllers:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataUpdated:) name:CSDataUpdatedNotification object:nil];
And implement dataUpdated: to update your data:
- (void)dataUpdated:(NSNotification *)notification
{
// Handle updates here
}
In the controller causing the change, post the notification:
- (void)updateData
{
// Data updating routine
// ...
[[NSNotificationCenter defaultCenter] postNotificationName:CSDataUpdatedNotification object:self];
}
You could set both as the delegate (i.e. two delegates) and re-use your modal view controller for both.
Alternatively, use NSNotificationCenter, but I think the delegate method is better, because the relationship is closer this way. which is the way to go if you want to message more than one object.
Thierry,
being new to iOS, I wouldn't call this an "answer", but using a global notification system does not sound right for this kind of problem to me.
Looking for an answer to a similar problem, I stumbled over references to NSFetchedResultsController, which will calculate results for you, readily to be used as a UITableView model - only reading knowledge. The part relevant to your problem seems to be its delegate, NSFetchedResultsControllerDelegate, which defines a few methods which will allow to communicate changes to the result to any number of interested parties.
But as I said, I just stumbled over it, and are just now trying to make use of it.
Regards, nobi

Objective-C object dealloc'd while other objects still has a delegate reference to it causes crashes. How to prevent this?

I have an app with a navigation controller as the root view. There are many views that can be pushed in.
The user has to create an account to use the app. The user can then log into this account from other devices, but only one device can be logged onto the same account at a time. So if multiple devices try to log into an account, only the latest device will be logged in and the other devices are logged off (a push is sent to the devices).
Since there are multiple views that the device could be showing before it was logged off, I call popToRootViewControllerAnimated: to get back to the root view. This is because when the user logs in the next time I only want the root view to be shown (the new account might not have access to the previously shown view).
If the user has an alert view or action sheet presented (which uses the current view as its delegate) before the push is received, the view will still be shown after the popToRootViewControllerAnimate: method is called. If the user then taps on a button for the alert view or action sheet, it will send a message to the dealloc'd view and crash the app.
An example:
myViewController is being shown to the user.
myViewController create an action sheet prompting the user for a decision.
The push is received for the device to log out.
The navigation controller pops all the views controllers and now shows myRootViewController.
Since the view controllers are popped, myViewController is now dealloc'd.
The action sheet from myViewController is still shown.
When the user selects an option form the action sheet, a message is sent to myViewController, and since it is already dealloc'd, a crash will occur.
Is there any way to prevent this?
One solution I have considered would be to keep track of all the objects that uses a specific view controller as its delegate. Then when that view controller dealloc's it will also set all the object's delegates to nil. This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list.
Any better solution (or improvement to mine) would be appreciated!
Edit: The alert view and action sheet are only examples of some objects that I would use myViewController as a delegate. I am also using a number of other classes (and third-party libraries) that implements this delegate pattern.
A few ideas:
you can encapsulate the alert/action sheet view and delegate in a single class. Then when you need an alert view, create MyAlertView instead, which will also be its own delegate and will do [self release] after the user taps a button.
make your App Delegate the only delegate for all your alert views and action sheets. App Delegate is always around while the application is running, so there won't be a problem with a released delegate.
The problem with both solutions is that if you need your application to know what happened in the alert view/action sheet, you somehow need to tell the interested class of the user's choice.
You can do that by either using delegates of your own - which would mean you're back to square one - or use notifications: when the alert view/action sheet delegate is called, it would post a notification ([[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:self userInfo:userInfo];), while the interested object would look for that notification ([[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotification:) name:NotificationName object:nil];) and perform whatever tasks necessary in onNotification:(NSNotification*)aNotification method.
You'll be able to agree with yourself on what type of information is passed in those notifications (I would think the button number in a NSNumber class would be enough, or perhaps pass the button text, too). And you won't have to keep track of all alert views - just don't forget to remove the observer ([[NSNotificationCenter defaultCenter] removeObserver:self name:postNotificationName object:nil];) in the views' dealloc.
Edit:
"This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list."
Actually you probably can do this in a semi-automated way: make a singleton object with a method like
-(id)delegate:(id)delegate for:(id)forWhom
And then instead of
someThingy.delegate = self;
you'd do
someThingy.delegate = [[DelegateLocker defaultLocker] delegate:self for:someThingy];
Inside the DelegateLocker you'd have a MutableDictionary with delegate class as a key and a MutableArray of someThingies as a value. Then in your view controllers' deallocs you'd call
[[DelegateLocker defaultLocker] delegateIsDying:self];
which would go through the thingies and assign delegate = nil for each
The drawback of course is that you'll be retaining all the thingies for an indefinite period of time instead of releasing them immediately.
So the ViewController that presented the action sheet iand set itself as the delegate right? So why dont you keep a reference to the ActionSheet in the ViewController, in the dealloc method of the view controller, you can check if the action sheet is visible, if it is then set the delegate of the action sheet to nil,and dismiss it...
so
-(void)dealloc
{
if(myActionSheet && [myActionSheet visible])
{
[myActionSheet setDelegate: nil];
//dismiss
}
}
Hope that helps
If you want automated solution, I think you can make a function to iterate through Ivars of your view controller to see if any Ivar has delegate property and set it to nil.

sending data to previous view in iphone

What are the possible ways to send data to previous view in iphone. Without using Appdelegate. Because there are chances for my view class to be instantiated again.
I believe the best approach is using the NSNotificationCenter class.
Basically what you do is register an object (as an observer) with a notification center.
So for example if you have objects A and B. A registers as an observer. Now lets say A is the "previous" object you are talking about, you can have B send a notification (data or message) to the notification center which then notifies object A (and any other registered observers).
Example:
In file ClassA.m register as shown below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSomething:) name:#"SomethingHappened" object:nil];
didSomething is the method which receives the notification sent by object B. This will look something like
- (void) didSomething: (NSNotification *) notify {
...
}
Finally you send the message below from whatever method in ClassB.m to notify/send data to object A
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingHappened" object:self userInfo:your_data];
Seems convoluted but it's the best approach in my opinion (and quite simple once you understand it :)).
There are several ways to achieve data sharing, with Singleton Objetcs being one of the most popular:
Objective C Singleton
If the view you want to communicate with is a parent view (e.g. the previous view's view controller is where you created this view) then you probably want to handle dismissing the view in the previous view controller. When you do that, you can read the data that has changed and update the previous view controller with the new data.
Then in the viewWillAppear: method of the previous view controller, update the actual views to reflect the current state of the view controller.
Edit: I've just noticed that your newView is transparent. If this is the case, then you certainly want to route all logic through your view controller. You should only have one view controller with visible views at a time.

Loading tableview from another class in iPhone

I have a RootViewController class and a UserSettingsController class. I have defined the UITableView methods (numberOfRowsInSection, cellForRowAtIndexPath) in the RootViewController class.
I want to reload the table view defined in the RootViewController class from the UserSettingsController class. How can I get control of the tableView Object in the RootViewController class from the UserSettingsController class?
I tried the following, but it tries to load a new tableview object.
RootViewController *rootViewController = [[RootViewController alloc]init];
[rootViewController.mytableView reloadData];
[rootViewController autorelease];
You can reload rootViewController.mytableView in viewWillAppear method of RootViewController itself. This will make rootViewController.mytableView reload when you are about to go to the rootViewController view. If the data you want to load is not much (as in takes more time to load like fetching data from the web) you will be fine with this solution.
Otherwise, to load rootViewController.mytableView from you settings view, you can use NSNotification like this:
In RootViewController.m :
//This goes in viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadTableViewData) name:#"ReloadRootViewControllerTable" object:nil];
Then make a method like this:
-(void) reloadTableViewData{
[mytableView reloadData];
}
In Settings view, where you want to reload the RootViewController tableView, write this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadRootViewControllerTable" object:nil];
This will automatically call the reloadTableViewData method of RootViewController without your need to do subclassing or anything. :)
Make use of notifications with custom name to call static methods in other classes. They are very handy.
I am presuming if what you want to do is to show the same exact table in two different view controllers.
Make myTableView a global or singleton and share it as a property between rootViewController and userSettingsController. Better yet, do a little more work and create a class for your myTableView so you can more easily set it up and manipulate it without having to spread duplicate code in the view controllers' implementation.
In each view controller, test to see if (myTableVIew == nil), and if so, go ahead and initialize and set it up (preferably by going through the tableview class you made). Once set up, you need to make sure you add myTableView to the properties (retained) of both view controllers. Then to display this tableView in each of the controllers, you will just have to do a [self.view addSubview:myTableView]; where self is the view controller that is active at the time.
Yes mahboudz you are absolutely correctly saying that tableview valuw ould be nil
Because if reinitialize the class from another class using init , the objects are reset to nil which are previously set.
And than when you want to reload it it doesn't works because the value of tableview is nil
I didnot get better answer for resolving this please let me know if any buddy knows the solution