I have two View Controllers: SavePopOverVC and MainVC. I also have a nib file called SavePopOver. SavePopOver has three items, a UIButton, a UIImage and a UITextView. The image and text view have outlets to property fields in SavePopOverVC called captionImage and captionTextView respectively. The button has an outlet to an IBAction in SavePopOverVC.
In MainVC.m I have the following two lines in my class extension.
SavePopOverVC *spvc;
UIPopoverController *popover;
In my viewDidLoad of the same file I have the following lines relating to my popover.
spvc = [[SavePopOverVC alloc] initWithNibNamed:#"SavePopOver" bundle:nil];
popover = [[UIPopoverController alloc] initWithContentViewController:spvc];
In my function that displays my popover, also in MainVC.m, I have the following lines.
[popover setPopoverContentSize:CGSizeMake(600,200)];
[popover presentPopoverFromRect:_header.frame inView:self.view permittedArrowDirection:UIPopoverArrowDirectionAny animated:YES];
[((SavePopOverVC *)popover.contentViewController).captionTextView setText:#"Some text here"];
However, captionTextView is nil when I make the setText: call. The app doesn't crash but the text isn't set. After the popover is displayed and I click on the UIButton to save the string typed in captionTextView I get the string just fine. So, I know the two are ultimately linked correctly, but how can I set captionTextView from when I display the popover?
If it is worth noting, I'm developing solely for iPad with this one.
It is most likely nil because its view isn't loaded at the time you set the text. Unlike most other modern languages, in Objective-C calling a method on a nil object doesn't cause an exception, it just does nothing.
To solve this, you can create a custom NSString property in your SavePopOverVC, e.g.
#property (nonatomic, copy) NSString *caption;
Before you call presentPopoverFromRect:, assign a value to this property. Inside SavePopOverVC, override viewDidLoad and set the captionTextView.text = self.caption;
There might be people who disagree with me, but I don't recommend exposing UI controls as properties in a view controller. This behaviour is one of the reasons for that.
Related
I am new to iOS development and I am currently reading the book : iOS Programming (Objective C) by Big Nerd Ranch.
I am confused as in where to initialize subviews such as UIButtons, UIImageView while creating views programtically:
Should the intialization be done in the Main UIView i.e in the
initWithFrame method and maintain a additional weak reference to the subview in the UIView.
or
should I do it in the UIViewControllers loadView method and maintain a weak reference to the subview in the uiviewcontroller (Same approach used while creating UIVew using the interface builder).
I have seen both the approaches being used in various stackoverflow posts but no post that explains which approach is the right one.
you can initialize as per your app's requirement. If any view or button or anything is part of initial setup of your app then you should initialize it in viewDidload.
Now, for example there is requirement like user press button and then new view will be created then you can initialize view in button's click method etc.
So, it's depends on your requirement.
Static views which will live from start to and of app should be initialize in viewdidload, because this is the first method getting called of viewcontroller.
hope this will help :)
It dependes on which architecture you are using. Apple raises the flag of Model-View-Controller, but in fact, UIViewControllers are the View.
For Example:
Let's say that you have a pretty LoginViewController. When you instantiate it, you will be doing something like
LoginViewController *loginVC = [[LoginViewController alloc] init];
At this point, no view is loaded. Your ViewController has just executed the init method, nothing else. When the system calls
loginVC.view
the first method to be executed will be
- (void)loadView;
there you should do exactly that, load your view. So, the approach i like is to have an additional LoginView.
- (void)loadView
{
// you should have a property #property (nonatomic, strong) LoginView *loginView;
self.loginView = [[LoginView alloc] init];
self.view = self.loginView;
}
and in the LoginView init method, you should put your code to build up the view.
However, you could eliminate LoginView, and instantiate all your subviews like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
UIButton *button = [[UIButton alloc] initWithTargetBlaBlaBla...];
[self.view addSubview:button];
// add more fancy subviews
}
In my experience, the first approach is much cleaner than the second one. It also makes version control a lot easier (try to merge a xib, I dare you). I always use MyView.m to build the view (a.k.a setup constriants, style) and use MyViewController.m things like animations, lifeCycle. I like to think that MyView.m is the programatic xib, so anything that you can do with xibs, you should me able to do it inside your view.
Hope it helps!!
I am a beginner in "iOS", as will become clear from this question!! I am writing unit tests for my iOS app. I am trying to set the UITextField from the test class. Every time I set the username textfield text from the test case it returns null. Is there no way I can do this? I don't really want to change the code in my controller class for a test!
All the examples online create an instance of a class and set the text like below except using #synthesize (which I thought wasn't needed in "iOS7"), why is it returning null?
Code in Controller.h :
#property (nonatomic, retain) IBOutlet UITextField *username;
Test Case class :
SignUpViewController *viewController = [[SignUpViewController alloc]init];
viewController.username.text = #"username#example.com";
username.text =null
Since your property is declared using IBOutlet, I assume you're using storyboards for your views. If this is the case, then it's not an issue of the textField's text being nil, it's an issue of the whole textField being nil.
When a view controller is loaded from the storyboard, all your IBOutlets (provided they're hooked up correctly), are initialized for you. When running unit tests, there is no interaction with the storyboard, so your textField will not be initialized.
To get around this issue, you can create and assign the textField yourself:
SignUpViewController *viewController = [[SignUpViewController alloc]init];
viewController.username = [[UITextField alloc] init];
viewController.username.text = #"username#example.com";
Or, even better, you could take a look at OCMock, and create and assign a mock text field in your unit tests.
You need to load the view first.
SignUpViewController *viewController = [[SignUpViewController alloc]init];
[viewController view]; // !!
viewController.username.text = #"username#example.com";
Just because a property exists, doesn't mean the instance variable that backs it has been created. That particular UITextField is a reference to an object loaded from a NIB file (see the IBOutlet attribute),so you probably want to initialize the view controller using:
NSString *nibname = #"SignUpViewController"; // JUST GUESSING!
SignUpViewController *viewController = [[SignUpViewController alloc] initWithNibName:nibname
bundle:nil];
To get around this issue, you can create and assign the view of view controller yourself in unit tests class by writing this:
let _ = self.mockSubject.view
Here mockSubject is the viewController.
So, I have one ViewController that has a TableView populate with an Array (_vinhoArray) and another TableViewController that opens when a user taps a row in this first ViewController.
What I want is set the title of second View ( TableViewController) for the name of row selected.
In this first one I have this code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedRow = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.titleView = selectedRow;
}
In the second view a have a property set
#property (nonatomic, strong) NSString *titleView;
In the -(void)viewDidLoad I have this code
self.title = titleView;
But nothing shows in the title of the Second View (TableViewController)
Thats it! Please Help
If you simply want to set the title on the navigation bar of the view controller you are presenting, there's no need to create a property for it. You probably want to do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *titleString = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.title = titleString;
[self.navigationController pushViewController:subTable animated:YES];
}
Above seems to be the easiest and most straight-forward way to implement this. On a side note, it's pretty unconventional to name a variable type like an NSString with some other class type, like titleView or titleLabel. Something similar to "titleString", like I used, would be more appropriate and less confusing for anyone reading your code.
What is probably happening is that the view is loading before you set the titleView property. As you are setting the title statically in viewDidLoad, no changes are being shown. You can confirm this by putting in a couple of breakpoints and seeing the order of the calls being made.
One solution to this is to use a more dynamic method of setting the table. Create a custom accessor in your second view controller:
- (void)setTitleView:(NSString *)title {
_titleView = title; // This sets the backing iVar
self.title = titleView;
}
Now, whenever you set the property, then the change is applied, even if it is after the view has loaded.
You don't need to declare the method or the iVar, these are set up by autosynthesis; you are merely overriding the default implementation of the property setter.
Are you certain that setting .title property is what you want? If you're in a UINavigationController you may actually want
self.navigationItem.title
instead. Setting the title is independent of source of the title, so I'd work on getting that second View Controller to display a static title you choose, and only then turn to the task of passing a title dynamically from the first VC. Your issue is likely in setting the title string, not in how you pass that string around from VC 1 to VC 2.
As obvious as it seems, the .title property of a UIViewController doesn't always actually cause a title string to be displayed… the rules for when that property is actually used get tricky.
I am assuming that "titleView" is nothing but of type NSString.
In your SecondViewController.h, synthesize your titleView property.
And NSLog it in your SecondViewController before assigning it to self.title just to check that it's getting some value we tried to assign.
Hope this helps!
I just witnessed a very strange issue where my view would ignore all of the delegate calls coming from a custom view because I called alloc/init on the item at the load. I'm curious as to why.
#synthesize customTextField;
-(void)viewDidLoad {
// by calling this alloc/init, none of the changes here actually update to the view
// everything is ignored from here on in.
// if I comment out the alloc/init line, everything works fine
self.customTextField = [[UITextField alloc] init];
self.customTextField.text = #"Some text";
// setting font and size as well
}
While I would still get calls to the text field delegate methods, none were linked to my specific text fields. I could not respond to just customTextField.
I do realize that calling alloc/init will give me a completely new instance of customTextField... but why wouldn't that new instance be linked to IB and my view?
Because IB linking != binding.
When you link a variable in IB, it's a simply sets the variable once on first load, that's it. It does no other special code to track any changes to it, for good reason.
For example:
You are designing a UITableViewCell, and if you have a cell that is selected, you must rearrange all of the content inside the cell. In this case, you determined it would be easier if you just recreated all of the subviews and re-added them into the view, so you do the following:
-(void) layoutSubviews {
if (cellIsSelected)
{
// custom button is an IBOutlet property, which is by default a subview of self
self.customButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[self someSubView] addSubview:customButton];
}
else {
// where is customButton located now? is it a subview of self or `someSubView`?
self.customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [self addSubview:customButton];
}
}
Thus, it is much easier for IB to say let's set this once, and let the programmer figure the rest out than for IB to try and track all changes made to an object and report them the to the UI.
viewDidLoad is called after your nib is loaded, and creating a new UITextField instance at this point will not be associated with your nib. If you're setting up new instances manually you also need to manually setup the delegates, and add them as subviews of your view.
The XIB file has no way of knowing that you are changing the reference. Consider the following piece of code
NSObject *myObjA = [[NSObject alloc]init]; //create object
NSObject *myObjB = myObjA; //assign reference <- this is the your case after xib load
myObjB = [[NSObject alloc]init]; //create object, myObjA still lives on.
It's basically the same that happens when you load your XIB file; You get the reference to the instantiated object (equals myObjB in above example). You can do with the reference what ever you please but you do not change the interface instance just by creating a new object.
I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.