Call popViewController from Class Method - iphone

I'd like to go back to my root view from within a class method of view 1. When in an instance method of view 1, i would just say
[self.navigationController popViewControllerAnimated:YES];
but since self doesn't apply in a class method, I am wondering how to accomplish this. Pertaining to the illustration below, I am currently in a class method of View1Controller.m and I'd like to get back to Rootview. Thanks.

You can declare another method:
-(void)closeThisViewController
{
[self.navigationController popViewControllerAnimated:YES];
}
Then use NotificationCenter:
[[NSNotificationCenter defaultCenter] postNotificationName:#"notif_closeThisVC" selector:#selector(closeThisViewController) object:nil];
Although as jonkroll said, you're dealing with view controller stuff, we don't understand why you would put view controller related code inside a class method.
Edit
Sorry bad code above.
I meant to say you can use NSNotificationCenter to post a notification:
-(void)postNotificationName:(NSString *)notificationName object:(id)notificationSender
Then in the same view controller declare a NSNotificationCenter observer:
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
My brain was quicker than my fingers, so I kinda combined the two into one when I tried to explain the solution :P
It should more like this:
// posting a notification with NSNotificationCenter
[[NSNotificationCenter defaultCenter] postNotificationName:#"notif_closeThisVC" object:nil];
In your viewDidLoad method somewhere (I recommend at the top), add this:
-(void)viewDidLoad
{
// adding an observer with NSNotificationCenter
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeThisViewController) name:#"notif_closeThisVC" object:nil];
...
}
Hope that helps.

If you don't want to refactor this from a class method to an instance method (and there are certainly cases where you wouldn't want to do that), I'd suggest you add a completion block parameter to your class method:
+ (void)doSomethingWithCompletion:(void(^)())completion {
/* Do your thing... */
completion();
}
- (IBAction)doSomething:(id)sender {
[self.class doSomethingWithCompletion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
This would allow you to cleanly separate the instance-less operation the class method performs from instance-specific dismissing of the view controller. You could also make the completion block accept an error object if the operation can fail.
You could do something similar with a delegate object or even by passing in the view controller to dismiss, but this design seems to offer the cleanest separation with the most modern feel.

There are going to be legitimate arguments that encourage you to refactor so that you do have access to the current view controller and can access the navigation controller via currentVC.navigationController. Remember, it can still be a class method, just give it an extra argument when you call it (or start the call chain that calls it) from the VC.
However, I also had to solve this in one of my apps, so I just made sure that the navigation controller was globally accessible to everyone, always via pointer ("weak ref")
If you declare a global variable like this (say, in "Navigation.h")
extern UINavigationController *gNavController;
and define it in your AppDelegate.m (pays to review distinction between declaration/definition if you're rusty on that):
UINavigationController* gNavController;
and then assign it when you start up in application:didFinishLaunchingWithOptions::
(assuming the delegate has a property called viewController that is your navigation controller):
gNavController = viewController;
Then as long as you #import Navigation.h, you'll always have access to the navigation controller. This also makes getting a handle to its view for popups/popovers much simpler.
This also assumes your nav controller is never released for the lifetime of the app (probably true unless you're doing something unusual).

Related

NSNotificationCenter with respect to ViewWillAppear and ViewWillDisapper

I have a simple viewController that I want to listen for UIKeyboardWillHideNotification. Therefore I have the following code:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden)
name:UIKeyboardWillHideNotification object:nil];
}
- (void) keyboardWillBeHidden
{
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
I'm trying to decide when to remove the viewController as an notification center observer. I only need to know about the UIKeyboardWillHideNotification when the viewcontroller is on screen, thus I'm thinking about adding the following:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Is this sufficient? Is there ever a chance that viewDidUnload or dealloc will get called while the viewController is still on screen? Note that I'm using a very basic UINavigationController for the flow of my app.
Registering the notification in viewWillAppear and unregistering it in viewWillDisappear seems to be a clean and symmetric solution to me.
Note that viewWillAppear can be called multiple times before dealloc (e.g. if another view controller is pushed onto your VC, or if you switch between tab bar controllers.) If you register the notification in viewWillAppear and unregister it only in dealloc then you will get duplicate registrations (compare Warning for iOS/iPhone users about duplicate NSNotification observations) and the registered selector is called multiple times for a single notification event.
I actually prefer the block-based observer registration method
addObserverForName:object:queue:usingBlock:
which returns an opaque object which is used for removing the observer again. Storing this return value into an instance variable of your view controller helps to keep track if the observer is already registered or not, and therefore helps to avoid duplicate registrations.
To answer your direct question, dealloc will never be called while your view is still on screen unless you directly call it which you shouldn't be.
dealloc will only be called when there are no strong pointers remaining that point to your viewController.
As Anoop Vaidya suggests, it is totally doable to put removeObserver in dealloc and be confident that dealloc won't get called while your viewController is on screen, and if it does... well you have much bigger problems than removing an observer
Edit: Since I can't actually reply to comments yet, when your viewController is off screen it is actually deallocated. It is then re-instantiated when it is called back on screen.
Edit: I'm wrong

Confused about self and delegation

This code is from Utility app, added by Apple when creating a project.
What's the difference between this:
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
And this:
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[controller dismissModalViewControllerAnimated:YES];
}
They both work, but the first one is the main code from Utility app, added by Apple.
So is there any difference, and why does self works? self is MainViewController, not FlipsideViewController. I have no idea, does it has something to do with delegation?
Thank you.
It all depends on which object is executing flipsideViewControllerDidFinish; if it is the same as the controller, then it is just the same.
In practice, what happens is that sometimes the class that is delegating (in your case FlipsideViewController), also implements the delegate protocol (ie., acts as a delegate). In such case, self and controller are the same. This would correspond to a delegate intialization (eg. in the init method) like:
self.delegate = self;
But you could have your delegate be a different class (eg., the application delegate, or whatever) and in this case they would be different. In this case you would say (eg in init):
self.delegate = [UIApplication sharedApplication].delegate;
In this case, you application delegate would receive the call to FlipsideViewController and the controller argument tells which object it refers to; in this case, self != controller.
Another case is when your delegate is acting as a delegate for more than one object; in this case, the controller argument tells which object is the delegating one.
In practice, within the delegate method implementation you can safely use the controller argument without much thinking.
I know that an answer has been already chosen as correct and Sergio have done a good job explaining delegation, but on this specific case the answer is far more simple. self and controller in this case are not the same thing (as you say also in the question). self is the presenting controller and controller is the presented one. So, the reason that both calls work is this:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, however, it automatically forwards the message to
the presenting view controller.
From Apple's documentation on UIViewController

How does iOS decide which objects get sent didReceiveMemoryWarning message?

I am working on an iPhone application in which a number of UIViews are dynamically added to and removed from the main UIWindow.
When simulating low memory errors in the simulator, I have discovered that not all view controllers receive the didReceiveMemoryWarning notification. Unfortunately, these are the controllers that would most benefit from implementing this method.
I cannot seem to find good information about where and how the method gets called. I have read mentions that it gets sent to "all UIViewControllers", but this is evidently not the case. Adding a breakpoint in one of the classes that do receive the notification wasn't particularly enlightening either.
This is a complex project but one way these views get added is:
- (void) showMyView
{
if(!myViewController){
myViewController = [[MyViewController alloc]init];
[window addSubview:myViewController.view];
}
}
MyViewController is a subclass of another class, MySuperViewController, which is itself a subclass of UIViewController. None of those classes have corresponding NIBs; view hierarchies are created programatically.
I am looking for pointers to how I can go about diagnosing the problem.
When you are using .view of the view controller directly, there's a high chance that your view controller won't receive many notifications because it's not the correct way of using view controller. The UIWindow is special case, because the window can automagically know the controller of the view and direct the message to the controller correctly.
However, when you detach your view from UIWindow, the view controller is also detached and not managed by UIWindow any more. I think this is the source of the problem.
I would suggest that you add a navigation controller or tab bar controller as your root view controller, and use that view controller functionality to switch between your child controllers. Note that you should not remove your view controllers when switching so they will be able to receive the messages appropriately.
You might also considering releasing your view controller when not used if initialization of your view controller is trivial and not consuming too much time.
Somewhere in your code you are probably doing something like this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
The only safe place to do this is in -dealloc.
Everywhere else, you should specify the notification that you want to unregister for (this will still potentially break if you register for the same notification as a superlcass).
From the documentation
The default implementation of
[didReceiveMemoryWarning] checks to
see if the view controller can safely
release its view. This is possible if
the view itself does not have a
superview and can be reloaded either
from a nib file or using a custom
loadView method.
This method gets called when a Memory Warning "happens"/is simulated. When the memory is low, the system probably posts a notification and a view controller responds to the notification by calling didReceiveMemoryWarning.
If you do not override the method, the default implementation (described above) is called. All view controllers in memory receive the Memory Warning and call this method. They just don't do anything if it is not "safe" to release the view.
In a simple test application with a navigation controller, in both the current view controller and the one previously displayed, didReceiveMemoryWarning is called. I don't know how the NSNotificationCenter works exactly, but it knows who registered for the UIApplicationDidReceiveMemoryWarningNotification. It is probably set up something like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didReceiveMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
For more information, you can look at the Memory Management section in the UIViewController Class Reference.
I entered this question, searching for the right observer dealing with memory warnings. For those using swift, you can register as followed:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveMemoryWarning:", name:UIApplicationDidReceiveMemoryWarningNotification, object: nil)
With callback method:
func didReceiveMemoryWarning(notification: NSNotification){
//Action take on Notification
}
Also, make sure your custom class inherits from NSObject, or you'll be getting this error:
… does not implement methodSignatureForSelector: — trouble ahead

iPhone updating view

I am trying to solve a problem considering update of an view.
First I switch to another view by using:
- (void)viewSettings {
settingsViewController = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
[[self viewController] presentModalViewController:settingsViewController animated:YES];}
Which is a delegate Method called by
ivaskAppDelegate *mainDelegate = (ivaskAppDelegate *)[[UIApplication sharedApplication] delegate];
[mainDelegate viewSettings];
I switch back by calling another dellegate method
- (void)settingsDone {
[[self viewController] dismissModalViewControllerAnimated:YES];}
When I return to my view I now want to update a label, can you explain how to do it?
I use NIB-files which have a controller class and a view class connected in the identity inspector.
/N
Although I heavily suggest delegation in this case there are two other options, that come to my mind: Notification and KVO.
Notification
Whenever settings are changed the settings view controller could post a Notification, to let other parts of the app know about this change. Posting a notification is easy as:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SettingsChangedNotification" object:theNewSettings];
Every object that somehow want to know about a settings change can subscribe to that notification via:
//Subscribe (in viewDidLoad or init)
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(settingsChanged:)
name:#"SettingsChangedNotification" object:nil];
// Called when a "SettingsChangedNotification" is posted
- (void)settingsChanged:(NSNotification*)settingsChangedNotification {
id newSettings = [settingsChangedNotification object];
}
//Unsubscribe (in viewDidUnload or dealloc)
[[NSNotificationCenter defaultCenter]
removeObserver:self name:#"SettingsChangedNotification" object:nil];
See Notification Programming Topics
If you are trying to manages UserDefaults with your settingsViewController there's an even better way. Just set the values on the sharedUserDefaults and the app will post a NSUserDefaultsDidChangeNotification notification all on it's own. All objects that depend on user settings could subscribe to that notification, and after it's posted reread the userDefaults.
See
NSUserDefaults Class Reference
User Defaults Programming Topics
Key-Value Observing (KVO)
Your rootViewController could observe changes of an object, which it needs to synchronize with, by Key-Value Observing.
One object registers itself as observer for keyPaths on other objects by sending them a addObserver:forKeyPath:options:context: message. The object is informed about changes via the observeValueForKeyPath:ofObject:change:context: callback method. KVO could sometimes be difficult the get right, because you have to ensure, that you register and unregister the same number of times, before an object gets deallocated.
See Key-Value Observing Programming Guide
Conclusion
Please refrain from using "global" variables on your app-delegate. There's enough possibilities to do better. The sooner you dive into them, the better code you will write.
You will need to set up a delegate that is implemented in your main view controller (where you need to have the label updated), and that is called from your settings view controller. I have a blog post that describes how to do this:
http://www.dosomethinghere.com/2009/07/18/setting-up-a-delegate-in-the-iphone-sdk/
You would make your view controller conform to a protocol that your create. In this case it may be named SettingsDelegate. That protocol contains one message - (void)didFinishSettingsWithSomeNewValue:(id)newValue;
The Settings Controller has a delegate instance variable and a property of type id<SettingsDelegate>
Before you present the settings viewController you assign the parent view controller to the delegate property. In the settings view controller you send a didFinishSettingsWithSomeNewValue: message to your delegate (the parent view controller) along with some new value. The parent view controller implements that method and inside the implementation can dissmiss the modal controller and update any views on itself.

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