If I don't want to use a nib file, what is the proper implementation of loadView that sizes the root view correctly? - ios

There are a bunch of related questions here, but none that feels like a concise or correct answer. Here's the situation:
I am creating a new ViewController and don't want to use a nib file. My understanding from the Apple docs is that if I don't want to use a nib, I should implement loadView to manually create my own view hierarchy.
However, its not clear to me how I should properly instantiate self.view with the proper bounds (given this view controller might be used in a bunch of different situations, setting it simply to the screen bounds doesn't feel right).
Somehow the default UIViewController loadView does seem to properly initiate the frame size, but its not clear if I'm writing my own version what I should be doing to do this.

There is no need to implement loadView. Instead, implement viewDidLoad and create and add any and all desired subviews you want. Just add them to the default self.view.
If you want to add a view that fills the view controller's view then do something like the following:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *someView = [[UIView alloc] initWithFrame:self.view.bounds];
someView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:someView];
}

In loadView, you should set the view property of viewController, and nothing else. Adding subviews should be done in viewDidLoad.
- (void)loadView {
self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 80, 40)];
}
Roopesh Chander has an interesting blog post on which strategy to choose: loadView vs viewDidLoad
for Programmatic UI Setup. He recommends setting the frame in loadView rather than viewDidLoad for maximum efficiency.

Related

How to set backgroundColor in loadView?

I want to override the loadView method of my custom view controller in order to customise the view that is being shown. For that custom view I want to apply the background color that was set in the Storyboard editor.
If I for example implement loadView like this:
-(void)loadView{
self.view = [[MyCustomView alloc] init];
//Do some initialisation work for the custom view
//...
}
Then the resulting view has a black background. However, if I only call the default implementation:
-(void)loadView{
[super loadView];
}
Then the orange background color (see screenshot) is applied to the view. How can I get the color that was set in Storyboard editor? I would like to do something like this (pseudo code):
-(void)loadView{
self.view = [[MyCustomView alloc] init];
self.view.backgroundColor = [self colorThatWasSelectedInStoryboardEditor];
//Do some initialisation work for the custom view
//...
}
How does apple achieve that in the default implementation of loadView?
Since you apparently really want to use "loadView" here instead of "viewDidLoad", AND Apple's documentation for loadView says NOT to call "super", you will need to "hide" your custom color somewhere else. E.G. some other view (e.g. a hidden one?) in your ViewController, where it's connected to an IBOutlet and where you can extract it via the view's ".backgroundColor" property.
Or you will need to set your ".backgroundColor" property via good ol' UIColor methods like "colorWithRed:green:blue:alpha:".
All your views from the Storyboard are laid out before viewWillAppear:, which means, in viewDidLoad you don't have them yet. Try moving your code to viewWillAppear:, or even better, to viewWill/DidLayoutSubviews and than self.view.backgroundColor will give you the one you set in the IB.
To answer the question in your comment, go to the IB, select your view, go to the Identity Inspector tab under the Utilities (right) pane, and set a custom class to be MyCustomView. No need to implement loadView to do that.
e.g. a UIView that I have declared as a custom view class AGBlurView:

Where and when to initialize/create view programatically

I had this question when/where to create and initialize views that are created programatically, so I hope some discussions here will shed more light on this topic for me.
This slide:
says: "not to initialize something based on the geometry of the view in viewDidLoad" and suggests viewDidAppear.
Imagine my view controller has view. I want to add 10 dynamic UIButtons to it.
Shall I put the code like below to the viewDidAppear?
-(void) viewDidAppear:(BOOL)animated
{
...
UIButton *button1 = [[UIButton alloc] initWithFrame: rect1];
[self.view addSubview: button1];
UIButton *button2 = [[UIButton alloc] initWithFrame: rect2];
[self.view addSubview: button2];
...
}
But this creates the buttons each time the view is shown. Is it what we want?
On the other hand if I put the code in viewDidLoad slide suggest not to initialize geometry of these views there.
Or shall we create buttons in viewDidLoad and set their frames in viewDidAppear?
What approach do you usually take?
But this creates the buttons each time the view is shown. It's true.
So the best thing you can do is to add a boolean (lets name it isLaunched). You set it to FALSE in the method -(void)viewDidLoad
Then add a if condition in your -(void)viewDidAppear where you perform creation of buttons (or other stuff) and set the boolean to true at the end.
You should have something like that :
-(void)viewDidLoad
{
//some settings
isLaunched = FALSE;
}
-(void)viewDidAppear:(BOOL)animated
{
if(!isLaunched)
{
//creating and adding buttons
isLaunched = TRUE;
}
}
zbMax (and now Amar) offered good solutions to implement the view creations in viewDidAppear: I will provide the rational for doing this (over viewDidLoad).
It is pretty simple actually. In viewDidLoad none of the views are actually setup yet, so any attempt to set/create frames or bounds will be extremely inconsistent. Struts and springs (or autolayout) will take effect after this method which will create additional changes to your views. viewDidAppear: is the correct place to do this because you can now rely on existing views and setting frames.
Reason for not playing with the geometry in viewDidLoad is because view is still in the memory and not on the window. Once the view is put on the window, then you can specify geometry. That happens when viewDidAppear is called for your controller.
As recommended, you should do all the initialisation in viewDidLoad as this is one time task and need not be repeated. Hold references to the added subviews and give them appropriate frame in viewDidAppear.
When you are dealing with custom UIView and its subviews, layoutSubviews is the method you need to override in the custom view in order to rearrange the geometry of its subviews.
Hope that helps!

Add clickable and fixed subview to UITableViewController?

I'd like to place an ADBannerView object onto my UITableView screen statically, what means that I want it to always stay above my toolbar (self.navigationController.toolbar), even when the user is scrolling the tableview. I've solved this by adding by ADBannerView as a subview to my toolbar and given it negative values for the frames origin:
[self setBannerViewSize];
[self.navigationController.toolbar addSubview:bannerView];
The only problem is: I can't click and open the iAd this way - I can see the banner but nothing happens when I tap on it.
Since I'm also using a refreshControl, the option to use a UIViewController instead of UITableViewController and add a tableView manually wouldn't work for me. Is there any other way I can get my ADBannerView statically showing in my table view controller AND still being tappable?
Thank you in advice!
Yay!! After all I succeeded in solving this (really annoying) problem by myself (and a lot of reading around)!
First, I found this really world-changing post. Basically this post handles with the topic that a UITableViewController uses self.view for its tableView property, so overriding the tableView property (or synthesizing it manually) plus giving self.view a new view (from application) and adding tableView as its subview would make it possible to reach the real superview of tableView.
But this still didn't solve my problem, although I was sure it would, because it all made sense. My bannerView appeared in the right place (and was fixed) but it still didn't do anything when clicked. But there was a second minor thing I didn't know about:
As I read in this post the superview of a subview doesn't only have to be userInteractionEnabled but also have a non-transparent backgroundColor. Because my superviews background color was set to [UIColor clearColor] it all didn't work - but setting its backGroundColor to e.g. blackColor solved the whole problem: the bannerView got finally tappable! :)
So, my code is now looking like this:
#synthesize tableView;
- (void)viewDidLoad
{
[super viewDidLoad];
if (!tableView && [self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
[self resizeTableToFitBanner];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:bannerView];
// some other code
}
BannerViewController in Apple's iAdSuite sample code solves this problem very elegantly:
https://developer.apple.com/library/ios/samplecode/iAdSuite/Introduction/Intro.html
I think you should use a container view, and set things up in IB. You can add a tool bar and ADBannerView to the bottom of the view of your navigation controller's root view controller. Fill the rest of the space with a container view - this will give you an embedded view controller automatically. You should delete this one and then drag in a tableViewController and control drag from the container view to the tableViewController to hook up the embed segue.

Custom UIViewController and custom UIView

i have three views which have a couple of similarities. For example a UIImageView at position 30,30 and three UILabels at the same position.
Whats the best way to solve this without copy and paste or redundant code?
When i create all three views with a nib i have to copy and paste the same parts...
I started to implement my own UIViewController and a base UIView for that. My code looks like this:
- (id)init {
if((self = [super init])) {
image = [[UIImageView alloc] init];
header = [[UILabel alloc] init];
[self addSubview:image];
[self addSubview:header];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
image.frame = CGRectMake(30, 30, 20, 20);
header.frame = CGRectMake(30, 100, 200, 30);
}
When i push the controller with that view onto the stack, the view is displayed wrong. Image and label are covered by the titleheader (navigationcontroller)...
Can someone please explain me how this layout system works... ios sdk is the worst sdk i ever worked with :(
I don't see the necessity for copy and paste while creating views in a nib? It definitely removes the necessity to position views per code. And as aside comment: ususally the "worst" sdks are those that we don't know well.
And if they need a common set of housekeeping code factot a method out that will take a view as a parameter and do it without offending the DRY principle (donz repeat yourself)
When i create all three views with a
nib i have to copy and paste the same
parts...
Do you realize that it's possible -- simple, even -- to have an object like a view controller load more than one nib? All three of your view controllers could load the common part of the interface from the same nib file, and also load their own interfaces from their own nib files. One way to do that would be to put the outlet(s) for the common interface in one class along with whatever code (setup, actions, etc.) you need to manage the common interface. You could then create a subclass of that class for each different view controller, and have each subclass load its own nib. The last step is to either insert the interface from the subclass into the superclass's view hierarchy or insert the interface from the superclass into the subclass's view hierarchy, whichever makes sense in your situation.

UIScrollView won't show in UIView

My app is building purely programmatically on UITabBarController above a UINavigationController, which are both declared in my AppDelegate. Inside my navigationcontroller, I'm showing a UIViewController, a custom class. This custom class should show a custom UIView in a UIScrollView and a UIPageControl.
Heres my problem:
self.view seems to create an EXC_BAD_ACCESS error when I call it without declaring self.view = [[UIView alloc] init] (or similar). I was wondering if this was a problem with -(void) loadView but seems like it produces the same error in -(void)viewDidLoad. So I basically had to use self.view = scrollView to even show my scrollView, considering [self.view addSubview:scrollView] produced an error. My UIPageControl should stay on the page all the time, and actually be another part of the view than the UIScrollView. So I tried to add a container-view like this
Code:
container = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,0,0)];
scrollView.pagingEnabled = YES;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
// EDIT: ofcourse, I'm also resizing the frame with [scrollView setContentSize:] later, but this is farfetched code to add here.
[container addSubview:scrollView];
self.view = container;
Unfortunately, it seems that I don't get any result at all, and what appears is just an empty view. However, if I add a UILabel or similar, it shows:
[container addSubview:[[UILabel alloc] initWithFrame:CGRectMake(50,50,50,50)]]; // entered above or beneath the addSubview:scrollView line.
My question is: Why doesn't my scrollView appear in the UIView container?
I've noticed that some tutorials say that scrollView must have a delegate, and I agree with the logic - however I can't seem to find out how I set that delegate when I am in my CustomUIViewController-class instead of my AppDelegate.
after you change the UIScrollView size you should use:
[scrollView setNeedsDisplay:YES];
also you implement Delegates the same way you do in other classes:
.h:
#interface MyClass : NSObject <UIScrollViewDelegate>
Okay, the problem seemed to be the initialization - I didn't realize that frame and content was two different things. Seems like the frame that is initializing the view should be whatever size the view should fill, while content is the actual content of whatever should be scrolled. So when I was having problems with user interaction, it was really this.
The problem of why it didn't show in the first place was (stupid.) that the frame was initially, and never changed from, 0,0 so I really lied in my first post.
Thanks to UIScrollView and PageControl: space between views who solved my problem with user interaction.
My steps was to backtrace from self.view:
NSLog(#"%f\n%f",
((UIScrollView*) [[self.view subviews] objectAtIndex:0]).frame.size.width,
((UIScrollView*) [[self.view subviews] objectAtIndex:0]).frame.size.height);
when I realized these were 0 and 0, fixing the problem wasn't too hard :) thanks though, for your efforts Kristian.

Resources