I am trying to add a UIView to a view controller, but through an object of another class whose objects are currently present on the view controller in question.
myViewController is on screen -> It has objects of myNodeView added to it.
I click on myNodeView and call a method in myViewController class to add a view to it.
I instantiate using myVc = [[myViewController alloc]init];
and then call the method. Is this the problem, that this is a new instance and that's why it does not add to the view currently visible. Please help me out.
Code -
// in nodeView
-(void)loadMap{
if(myVc==nil){
myVc=[[MyViewController alloc]init];
}
[myVc loadMapView];
}
// in MyViewController
-(void)loadMapView
{
if(mapView==nil)
{
self.mapView = [[MapView alloc]initWithFrame:CGRectMake(0, 0, 400, 400)];
self.mapView.backgroundColor=[UIColor whiteColor];
}
[self.view addSubview:self.mapView];
}
You are creating a new instance of MyViewController each time you call loadMap method. You can do something like this:
// Getting current viewcontroller
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController)
topController = topController.presentedViewController;
// Call the method
MyViewController *myVc = (MyViewController *)topController;
[myVc loadMapView];
In MyViewcontroller, when you create the nodeView, you should be assigning the nodeView's myVc property, like this nodeView.myVc = self.
You should also make sure that nodeView's myVc property is assign.
You also don't need to check if myVc == nil in the nodeView.
I think make MyViewController is childViewController (Implementing a Container View Controller) is more predictable in behavior of views and events passed.
Related
Let's say you're pushing a new View Controller onto the navigation stack and need to set a UI property (ex: a UI label's text or something). When you initialize the VC, it's views are not set (so they could be nil). Therefore, setting properties won't work. For example:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.someUILabel.text = #"foo";
[self.navigationController pushViewController:vc animated:YES];
This won't set the UI label's text because vc.view and its subviews are nil. A couple of ways to remedy this are:
After calling init on the VC, do something like [vc view] which will load the view and then allow you to set properties.
Set a non-UI ivar and then in viewDidLoad set up the UI like so:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.uiTextLabel = #"foo";
[self.navigationController pushViewController:vc animated:YES];
// in viewDidLoad
self.someUILabel.text = self.uiTextLabel
Is there an accepted way to get rid of this problem? Is one of these better than the other or is there a different solution?
You should not set the label value from another view controler. A controller controls its views.
You should have a NSString property in you SomeViewController, and set that public property instead with the string you want. Then, in SomeViewController viewDidLoad method set the value of the label to the one in the property.
Initialize the views after pushing it in the stack and in the pushed vc not the one that pushed it.
I found that the most elegant method to do this is:
in the main VC:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.textForLabel = #"foo";
[self.navigationController pushViewController:vc animated:YES];
And in the pushed VC:
#interface SomeViewController : UIViewController
#property (nonatomic, strong) UILabel *textLabel;
#end
- (void)setTextForLabel:textForLabel
{
_textForLabel = textForLabel;
self.textLabel.text = self.textForLabel;
}
- (void)viewDidLoad
{
self.textLabel.text = self.textForLabel;
}
So basically you set the property as an NSString and then adjust the UI in two separate places.
I have a custom ViewController which is an instance variable of my root viewController.
I intend to modally present it whenever a button is touch. Therefore the viewController will be presented and dismissed potentially many many times.
I obviously only want to alloc init my instance variable once as the modal viewController is not deallocated each time it's dismissed, so should I have code like this inside my button action to ensure that it's only alloc and inited once?:
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
[self presentViewController:myViewController animated:YES completion:NULL];
I usually use lazy instatiation in those cases:
Declare a property for your ViewController:
#property(nonatomic, strong) UIViewController *myViewController;
After that you can override the get of myViewController
-(UIViewController*) myViewController {
if(!_myViewController) {
_myViewController = [[UIViewController alloc] init];
}
return _myViewController;
}
This way you guarantee that was only instantiated once and is always there when you needed.
ATTENTION
This works well if you always use self.myViewController. I consider a good practice that properties' generated iVars should only be accessed in their setters/getters.
You can use the following way to ensure that only one instance of the view controller active at a time.
if(myViewController) {
[myViewController release];
myViewController = nil;
}
myViewController = [[ViewController alloc] init];
[self presentViewController:myViewController animated:YES completion:NULL];
You need to make myViewController as class variable.
I have a custom view controller named CKCalendarViewControllerInternal.
CKCalendarViewControllerInternal
This class is the subclass of UIViewController.
CkCalendarViewController
I have a custom view controller named CKCalendarViewController. It's a subclass of UINavigationController as follow:
#interface CKCalendarViewController : UINavigationController <CKCalendarViewDelegate, UINavigationControllerDelegate>
This class is initialize with the CKCalendarViewControllerInternal as follow:
- (id)init
{
CKCalendarViewControllerInternal *calendarViewController = [CKCalendarViewControllerInternal new];
self = [super initWithRootViewController:calendarViewController];
}
Now, In AppDelegate my first view is as follow:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
UINavigationController *n1=[[UINavigationController alloc]init];
n1.viewControllers=[[NSArray alloc]initWithObjects:self.viewController, nil];
self.window.rootViewController=n1;
[self.window makeKeyAndVisible];
return YES;
}
CkDemoViewController
This class is the subclass of CkCalendarViewController as follow
#interface CKDemoViewController : CKCalendarViewController
ViewController.m
When i try to push the CKDemoViewController on button clicked.
Error & Question
It shows me error like
Exception: Pushing a navigation controller is not supported
Exception: [NSException]:Pushing a navigation controller is not supported
ex.name:'NSInvalidArgumentException'
ex.reason:'Pushing a navigation controller is not supported'
Reason for error
This is because the CKCalendarViewController is the subclass of UINavigationController.
If i try to open the modal view, it works perfectly.
But How can i initialize the CKCalendarViewController as shown above with the CKCalendarViewControllerInternal class??
Thank you,
Answer will greatly appreciate
If I understand correctly what you are doing, the simplest "hackish" way to make things work would be making CKCalendarViewController derive from CKCalendarViewControllerInternal. I am suggesting this because I see that you are trying to use your CKCalendarViewController as a normal view controller, so there should be no reason to have it be a navigation controller.
Another possibility would be for you to actually use your CKCalendarViewController as a navigation controller by doing this in your app delegate:
UINavigationController *n1 = [[CKCalendarViewController alloc]init];
n1.viewControllers = [[NSArray alloc]initWithObjects:self.viewController, nil];
self.window.rootViewController = n1;
but this depends on what you are trying to achieve.
More generally, if you are interested in "nesting" controllers within controllers, you should learn about controller containment. In controller containment, what you do to add a controller to another one is basically this:
[vc willMoveToParentViewController:self];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
I am using a BookController class which is using pagenumbers to keep track of the current view. Currently I am creating each view controller on demand and writing the code programmatically. I would like to access the view controllers that I have created in the StoryBoard (the xib files) so that when I demand a new page it will access a Second view controller I have created.
// Provide a view controller on demand for the given page number
- (id) viewControllerForPage: (int) pageNumber
{
if ((pageNumber < 0) || (pageNumber > 31)) return nil;
if(pageNumber == 0){
//here is where I want to access the entire xib file that the SecondViewController is connected with
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
SecondViewController *myVC = (SecondViewController *)[storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
myVC = [BookController rotatableViewController];
return myVC;
}
else if(pageNumber == 1){
// Establish a new controller
UIViewController *controller = [BookController rotatableViewController];
// Add a text view
UITextView *textview = [[UITextView alloc] initWithFrame:(CGRect){.size = CGSizeMake(100.0f,100.0f)}];
textview.text = [NSString stringWithFormat:#"This is dedicated to people"];
textview.font = [UIFont fontWithName:#"Futura" size:18.0f];
textview.center = CGPointMake(475.0f, 700.0f);
[controller.view addSubview:textview];
// Add a label
UILabel *textLabel = [[UILabel alloc] initWithFrame:(CGRect){.size = CGSizeMake(200.0f, 200.0f)}];
textLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
textLabel.text = [NSString stringWithFormat:#"1"];
textLabel.font = [UIFont fontWithName:#"Futura" size:18.0f];
textLabel.center = CGPointMake(475.0f, 985.0f);
[controller.view addSubview:textLabel];
// Add it as an image
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon#2x.png"]];
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
imageView.center = CGPointMake(160.0f, 230.0f);
[controller.view addSubview:imageView];
return controller;
}
Just not sure how to make a call to access that xib file i've created and make it into the first page (page=0). The second page (page =1) is an example of how i have drawn all the other pages in my book programmatically. Thanks!
Remember the Storyboard is just a collection of NIBs which simply instantiate the hierarchy of each view and connect the outlets to the owning view controllers. You do not want to instantiate the Storyboard yourself to just create a single view controller. What that is doing is creating new instances when the application has already been launched and is running with different instances. Even if you did have them wired up they would be wired to instances which are redundant and not the actual instances you want.
What I would do instead is create an individual NIB file for SecondViewController which you will use separately. Then you will need to wire it together. If this code is within the instance you need to access you would simply pass it along to a property on SecondViewController. Or maybe you just pass along values but most likely you will want to set a delegate property and define a protocol for SecondViewController to call back to the instance which created it.
For your code you can simply load the NIB with the following code.
SecondViewController *vc = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
vc.delegate = self;
You just need to define that delegate and possibly any properties you need to give data to the newly created view controller.
Below is an example of a delegate setup which I recently created for a SideBar interface using a Storyboard. I have a container view for the Header VC which is in the Home VC. This Header VC could be like your SecondViewController because I could not connect it in the Storyboard so I did it with code. First I created a delegate property on the Header VC.
#protocol IFHeaderDelegate;
#interface IFHeaderViewController : UIViewController
#property (nonatomic, assign) IBOutlet id<IFHeaderDelegate> delegate;
#end
#protocol IFHeaderDelegate <NSObject>
- (void)headerViewDidToggleSideBar:(IFHeaderViewController *)sender;
#end
Then when a button is tapped I use the delegate for the callback. (Notice I use an NSAssert to verify the delegate is defined just to give me a heads up if I missed it.)
#import "IFHeaderViewController.h"
#interface IFHeaderViewController ()
#end
#implementation IFHeaderViewController
- (IBAction)siderBarButtonTapped:(id)sender {
NSAssert(self.delegate != nil, #"Delegate must be defined!");
if (self.delegate != nil) {
[self.delegate headerViewDidToggleSideBar:self];
}
}
#end
But in order to wire it up I had to set the delegate from the Home VC which I could not do from the Storyboard. What I did was set it in the Home VC when the embed segue was fired in prepareForSegue.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
DebugLog(#"segue.identifier: %#", segue.identifier);
if ([#"HomeHeader" isEqualToString:segue.identifier]) {
NSAssert([segue.destinationViewController isKindOfClass:[IFHeaderViewController class]], #"Destination VC must be the Header VC");
IFHeaderViewController *headerVC = (IFHeaderViewController *)segue.destinationViewController;
headerVC.delegate = self;
}
}
You can find the full project on GitHub: https://github.com/brennanMKE/Interfaces/tree/master/SideBar
I have a navigation based application.On click of a button on the navigation bar in the first screen , I am able to push another view controller as follows :
-(void) buttonClicked:(id)sender
{
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
}
Now i have a UIView(separate .h and .m files) as part of the first screen. On click of a button in the UIView, i want to push the SecondViewController.
I have tried the following :
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
It doesnt work!! Kindly help
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
The problem here is that home isn't part of the navigation stack, so [home navigationController] is surely nil. I'm not quite clear on what you're trying to do here, but just creating a view controller doesn't mean that it's actually part of the view controller graph.
Why would it work? Randomly creating view controllers whose view is not even visible, is not the solution. You can either keep a reference to the VC in the view like this:
#imlementation ViewController
- (id) init
{
// ...
aView = [[CustomView alloc] init];
aView.viewController = self;
// ...
}
#end
#interface CustomView
#property (assign) ViewController *viewController;
#end
Or you can search the responder chain at runtime:
UIResponder *next = [view nextResponder];
while (next)
{
if ([next isKindOfClass:[ViewController class]])
{
break;
}
next = [next nextResponder];
}
And now "next" will contain the view controller (or nil if it can't be found).
Try using the same navigationController to push view, this keeps the same stack of ViewControllers.
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
[mv release];
I see your problem now! You need to #import your FirstViewController, then #class it. Then do your push.
So:
//.h
#import "FirstViewContoller.h"
#class FirstViewController;
#interface...
//.m
-(void)return {
FirstViewController *firstview = [[FirstViewController alloc]init(withnibname:)];
[firstView.navigationController pushViewController: firstView.navigationController.topViewController animated: TRUE];
}
If I am not wrong, your UIView though is in separate files, is still added to the screen from a UIViewController class.
Simply, post a notification from UIView to your FirstViewController class where you have access to the navigation controller. Then push the SecondViewController from there.
You Can use this. It Works very well for me:-
Firstly Create Object of AppDelegate in UIView Class and initialize it. Then create Navigationcontroller object in Appdelegate.h :-
#property(strong,nonatomic) UINavigationController *navControl;
In your UIView Class implement this code where you want to push :-
ViewController *objview = [[ViewController alloc]init]; [appDelegate.navControl pushViewController:objview animated:YES];