I am using the iPhone toolchain on Linux and so I have no Interface Builder. So how could I layout my view in my ViewController subclass? For example, I want a UITextView in the middle of the screen? Should I do this in the loadView or viewDidLoad. Do I also have to set the view for the ViewController subclass to itself?
It is not an easy job to layout all the view using code. Here are some code:
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake (100, 100, 100, 100)];
[self.view addSubview:textView];
The frame is the place (the first and second argument is x and y coordinator) and the size (the third and fourth argument is width and height of the text view).
Using this way, you can add any view into your class. Some of the view is built in and you don't have to draw yourself, some of them is not, and you need to subclass UIView and override drawRect.
You should do this in viewDidLoad when your main view controller is finished loading
I've written an open source project that does exactly this:
https://github.com/charlesmchen/WeViews
Here's another project that you might find useful:
http://code.google.com/p/layoutmanagers/
I usually build the entire view hierarchy in the loadView method and perform additional setup in the viewDidLoad, for example to set up the subviews content to reflect the data associated to the view controller. The important thing is to set the view controller view outlet in the loadView method.
#synthesize label; // #property(nonatomic,retain) UILabel *label declared in the interface.
-(void)loadView {
// Origin's y is 20 to take the status bar into account, height is 460 for the very same reason.
UIView *aView = [[UIView alloc]initWithFrame:CGRectMake(0,20,320,460)];
[aView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[aView setAutoresizeSubviews:YES];
// The 150x50 label will appear in the middle of the view.
UILabel *aLabel = [[UILabel alloc]initWithFrame:CGRectMake((320-150)/2,(460-50)/250,150,50)];
// Label will maintain the distance from the bottom and right margin upon rotation.
[aLabel setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin];
// Add the label to the view hiearchy.
[self setLabel:aLabel];
[aView addSubview:aLabel];
// Set aView outlet to be the outlet for this view controller. This is critical.
[self setView:aView];
// Cleanup.
[aLabel release];
[aView release];
}
-(void)viewDidLoad {
// Additional and conditional setup.
// labelText is an istance variable that hold the current text for the label. This way, if you
// change the label text at runtime, you will be able to restore its value if the view has been
// unloaded because of a memory warning.
NSString *text = [self labelText];
[label setText:text];
}
-(void)viewDidUnload {
// The superclass implementation will release the view outlet.
[super viewDidUnload];
// Set the label to nil.
[self setLabel:nil];
}
The biggest difficulty is probably understanding how IB settings map to UIView variables and methods, for example the autoresizing mask. Apple's UIView and UIViewController class references are full of useful informations.
Related
Here is a related question I found, but it does not answer my question in detail.
[http://stackoverflow.com/questions/3209993/cocoa-touch-can-i-have-multiple-views-per-view-controller-or-specify-bounds-of][1]
I have a UIView class, BallView, which is set to be the default view of the ballViewController. Now, this view has a ball bouncing around according to the accelerometer. I am calling a private function draw every time the accelerometer sends updates.
However, my main question is: I would like to have multiple such balls bouncing around.
Do I have to recreate the view for every class ? But then the File's Owner's IBOutlet view will also have to be connected. And an IBOutlet can point to just one address.
Any other way round this ?
Here is how I'm instantiating the Ball View class in the ballViewController:
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:
^(CMAccelerometerData *accelerometerData, NSError *error){
[(BallView *)self.view setAcceleration:accelerometerData.acceleration];
[(BallView *)self.view performSelectorOnMainThread:#selector(draw) withObject:nil waitUntilDone:NO];
}];
Thus, it means, my question is a bit different from those multi-view tab-bar solutions. Because in those cases only 1 view is shown at a time. I want 4-5 views overlaid on top of each other.
Any help ?
You're right, your view controller can only have a single UIView in its view property. That view though can certainly be used to contain other subviews.
What I would do is have a plain old UIView as your controller's view, and have your BallViews be subviews of that view. Your controller can still control those views, they ust can't all be in its view property.
EDIT: If you're using nib files/Interface Builder, adding a BallView as a subview of your controller's view is pretty easy - just drag a UIView object onto the view, and in the identity inspector you can change the identity of the view to your BallView class.
If you're not using IB, you can also do the same programatically:
// BallViewController.h
#interface BallViewController
{
BallView* ballView;
}
#end
// BallViewController.m
#implementation BallViewController
- (void) loadView
{
...
CGRect frame1 = ...
CGRect frame2 = ...
self.view = [[UIView alloc] initWithFrame:frame1];
ballView = [[BallView alloc] initWithFrame:frame2] retain];
[self.view addSubview:ballView];
...
}
#end
I've always used IB but am trying to do everything through code and I'm failing at this task.
I have a ViewController to handle User Inputs and 2 UIViews which will both be visible at the same time(each in a separate header/implementation UIView file):
1 UIView represents a custom tab bar that changes (bottom 50 px)
1 UIView represents the displayed interface (everything above the tab bar)
Each needs to exist within its own frame, initialized from the ViewController so it can control them and what they display.
Bra, UIViewControllers have only one UIView as part of their guts.
That is, "view" ... i.e. ... the actual property view, as in self.view = something or view.hidden = YES.
However you can, of course, add as many subviews as you like to that view.
This is how views are used normally. Almost every .view has subviews inside it.
UIView *bottomThing = [[UIView alloc] init];
bottomThing.frame = CGRectMake whatever
UIView *otherThing = [[UIView alloc] init];
otherThing.frame = CGRectMake whatever
[view addSubview:bottomThing];
[view addSubview:otherThing];
In the example, we added two subviews to our main "built-in" view, which you refer to as simply "view". So we added bottomView to our "view" and we added topView to our "view."
The subviews you add could be either plain old UIView, or, your own special subclass of UIView.
MySpecialView *bottomThing = [[UIView alloc] init];
bottomThing.frame = CGRectMake whatever
ExtraordinaryView *otherThing = [[UIView alloc] init];
otherThing.frame = CGRectMake whatever
[view addSubview:bottomThing];
[view addSubview:otherThing];
(I guess FTR conceivably you could subclass UIViewController to have more than one view inside it, but that's completely pointless and irrelevant to this question.)
From your UIViewController you can manipulate the subviews in any way you want.
For example [bottomThing doStuff:3.7], bottomThing.hidden=YES, etc etc.
Once again it is absolutely normal to add more subviews inside your main "view" - this is the basic way in which iPhone apps are made. There is only one ".view" - you add more views inside it as you wish. Hope It Helps.
UIView *myView = [[UIView alloc] init];
[self.view addSubview:myView];
[myView release];
I want to create a 'detail view' in my navigation-based app similar to the address book app.
I want to create a UIView that has an UIImageView and a UILabel that I can pass to a UITableVIew's tableHeaderView property when pushed by a navigation controller. The label text and image must take information from the managed object context when it loads.
I started trying to do this using IB, but go fed up when the label text wouldn't update, regardless of where I put the myView.UILabel.text = #"some new text". It just kept presenting the text I entered in the IB inspector window.
So without using IB, how would I go about creating a UIView that has a UILabel and a UIImageView in it?
Should I be creating a new class that is a sub-class of UIViewController or just UIView?
How do I go about creating the sub-views for the label and image?
Where should the text and image URL be set in code? (viewDidLoad or loadView or viewWillLoad)(UIView, UIViewController, detailViewController)?
If you started using IB and the UILabel text wouldn't update, sounds like the IBOutlet of your view controller isn't correctly connected to the label in IB.
Hopefully that will fix your problem with using IB. And in the viewController code, you should update that text in viewDidLoad.
But if you want to do this programmatically, I would make a subclass of UIView (not UIViewController).
And I would override initWithFrame and do all the setup there.
Something like the following:
myView.m
#implementation myView
#synthesize imageView, myLabel;
- (id) initWithFrame:(CGRect) frame
{
if (self = [super initWithFrame:frame]) {
// Setup your image view and add it to the view.
self.imageView = [[[UIImageView alloc] initWithFrame:initWithFrame:frame] autorelease];
self.imageView.image = ...
[self addSubview:self.imageView];
// Setup your label
self.myLabel = [[[UILabel alloc] initWithFrame:frame] autorelease];
self.myLabel.text = #"whatever you like";
[self addSubview:self.myLabel];
}
return self;
}
Make sure to clean up memory properly in the dealloc method of course depending on whether you like make class properties vs class variables.
I have a core data application which uses a navigation controller to drill down to a detail view and then if you edit one of the rows of data in the detail view you get taken to an Edit View for the that single line, like in Apples CoreDataBooks example (except CoreDataBooks only uses a UITextField on its own, not one which is a subview of UITableViewCell like mine)!
The edit view is a UITableviewController which creates its table with a single section single row and a UITextfield in the cell, programatically.
What I want to happen is when you select a row to edit and the edit view is pushed onto the nav stack and the edit view is animated moving across the screen, I want the textfield to be selected as firstResponder so that the keyboard is already showing as the view moves across the screen to take position. Like in the Contacts app or in the CoreDataBooks App.
I currently have the following code in my app which causes the view to load and then you see the keyboard appear (which isn't what I want, I want the keyboard to already be there)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[theTextField becomeFirstResponder];
}
You can't put this in -viewWillAppear as the textfield hasn't been created yet so theTextField is nil. In the CoreDataBooks App where they achieve what i want they load their view from a nib so they use the same code but in -viewWillAppear as the textfield has already been created!
Is there anyway of getting around this without creating a nib, I want to keep the implementation programatic to enable greater flexibility.
Many Thanks
After speaking with the Apple Dev Support Team, I have an answer!
What you need to do is to create an offscreen UITextField in -(void)loadView; and then set it as first responder then on the viewDidLoad method you can set the UITextField in the UITableViewCell to be first responder. Heres some example code (remember I'm doing this in a UITableViewController so I am creating the tableview as well!
- (void)loadView
{
[super loadView];
//Set the view up.
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
//Create an negatively sized or offscreen textfield
UITextField *hiddenField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, -10, -10)];
hiddenTextField = hiddenField;
[self.view addSubview:hiddenTextField];
[hiddenField release];
//Create the tableview
UITableView *theTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
[theTableView release];
//Set the hiddenTextField to become first responder
[hiddenTextField becomeFirstResponder];
//Background for a grouped tableview
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Now the the UITableViewCells UITextField has loaded you can set that as first responder
[theTextField becomeFirstResponder];
}
I hope this helps anyone stuck in the same position as me!
If anyone else can see a better way to do this please say.
Try do it in viewDidAppear method, works for me.
I think the obvious solution is to create the textfield in the init method of the view controller. That is usually where you configure the view because a view controller does require a populated view property.
Then you can set the textfield as first responder in viewWillAppear and the keyboard should be visible as the view slides in.
have you tried using the uinavigationcontroller delegate methods?:
navigationController:willShowViewController:animated:
I have a UITableView which is not being resized properly using autoresizeMask (in iPhone 3.0).
The UITableView is inside a UIViewController inside a UINavigationController inside a UITabBarController, all of which are being created programatically. The status bar is visible.
The code of the UIViewController is basically:
- (void)loadView {
UIView* rootView = [[UIView alloc] init];
self.view = rootView;
[rootView release];
}
- (void)viewDidLoad {
[super viewDidLoad];
// table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-20-49-44)];
table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)]; table.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self.view addSubview:table];
}
When created like this, the UITableView is slightly bigger than the available space. If I'm not mistaken, it's exactly 44 pixels bigger, the size of the navigation bar.
However, if I uncomment the commented line and comment the next line the size of the UITableView is exactly right. I would prefer to use autoresizingMask instead of manually calculating the size of the UITableView. What am I doing wrong?
Thank you in advance!
The problem seems to be that I wasn't setting the frame of the root view in loadView. If you define such frame, and then define the frame of the subviews in relation to that frame, then the autoresize masks will correctly resize the subviews according to how the root view was resized by the framework.
For example:
- (void)loadView {
UIView* rootView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
self.view = rootView;
[rootView release];
}
- (void)viewDidLoad {
[super viewDidLoad];
table = [[UITableView alloc] initWithFrame:self.view.frame];
table.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self.view addSubview:table];
}
Thanks to Colin Gislason who pointed me in the right direction.
The autoresizing mask will not help you with the initial size of the table view. The table view is created with the frame that you give it. The autoresizing mask defines the rules for resizing this frame relative to the parent view when the parent's frame changes.
So if I define a table that is 320x100 it will stay that size unless I change it explicitly or the parent view's frame changes.
Depending on the other views, you could do the calculation based on the other views held by the parent or by the parent's frame itself.
Create UIViewController subclass instand of UITableViewController Subclass.
insert UITableView instance.
in NIB simply drag and drop UIView
on top of that wier place the existing UITableVIew object.
set the size of the uitableview via nib or viewDidLoad method.
set the reference , dataSource and delegate via nib.
now its simply transfer the UIViewController class and the can change tableview size as you wish.