Multiple Views in Xcode 4.2 - objective-c

I'm having a lot of trouble finding a tutorial for implementing multiple views in Xcode 4.2 without storyboard, this is for a class so I can't use storyboard yet. I'm just trying to have a 2nd view with a UIPicker come up when a button is clicked in the main view, I just can't find one for this version of Xcode and it's different enough from the older versions to confuse me.
Any help appreciated if someone can give me a quick description of what I need to do this or a newer tutorial I'd appreciate it :)

I think you should read the UIView Programming Guide to get a good handle on how UIViews work exactly. I find nibs/storyboard are really great at confusing new iOS developers.
In essence, a UIViewController has 1 view which you set in the viewDidLoad or loadView method by using the [self setView:someUIView]. You add more stuff to the screen by adding UIViews as a subview of the viewcontroller's "Main" view. For example
-(void)loadView {
// Create a view for you view controller
UIView *mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self setView:mainView];
// Now we have a view taking up the whole screen and we can add stuff to it
// Let's try a button, a UIButton is a subview of UIView
UIButton *newButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// We need to set a frame for any view we add so that we specify where it should
// be located and how big it should be!
[newButton setFrame:CGRectMake(x,y,width,height)];
// Now let's add it to our view controller's view
[self.view addSubview:newButton];
// You can do the same with any UIView subclasses you make!
MyView *myView = [[MyView alloc] init];
[myView setFrame:CGRectMake(x,y,width,height)];
[self.view addSubview:myView];
}
Now here we have our viewController who'se view is just a plain UIView which in turn has 2 subviews; newButton and myView. Since we created the MyView class, maybe it contains subviews as well! Let's take a look at what a UIView subclass could look like:
// Here is the init method for our UIView subclass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Let's add a button to our view
UIButton *newButton2 = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// Of course, the frame is in reference to this view
[newButton2 setFrame:CGRectMake(x,y,width,height)];
// We add just using self NOT self.view because here, we are the view!
[self addSubview:newButton2];
}
return self;
}
So in this example we would have a view controller who'se view now contains 2 button! But the view structure is a tree:
mainView
/ \
newButton myView
\
newButton2
Let me know if you have any other questions!
Matt

Related

In UIViewController's code, [self.subViewGrid setNeedsDisplay] not calling -drawRect

I have an iPad app, using Storyboards, XCode 4.6 and iOS 6.1. I have a scene that contains a UIViewController. Inside that UIViewController, I have a UIScrollController, all created using IB. Programmatically, in viewDidLoad I created two (2) UIViews (one called subViewGrid, the other called subViewData) and added them to the UIViewController; they both display correctly in the Simulator. Here's the code:
- (void)viewDidLoad {
[super viewDidLoad];
// notify me when calendar has been tapped and CFGregorianDate has been updated
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(calendarTapNotification:)
name:#"calendarDateSelected" object:nil ];
// UIScrollVIew settings
CGSize scrollableSize = CGSizeMake(760, 1379); // set size of scheduleView
[self.schedScrollView setContentSize:scrollableSize];
self.schedScrollView.contentInset = UIEdgeInsetsMake(0,0,44,44); // allow for scroll bar
self.schedScrollView.directionalLockEnabled = YES; // prevents diagonal scrolling
// create a sub-view to hold the appointment GRID
CGRect frame = CGRectMake(0,0,760,1390); // 110,48,760,1390
subViewGrid = [[UIView alloc] initWithFrame:frame];
subViewGrid.tag = 12; // use tag to get correct sub-view
subViewGrid.backgroundColor = [UIColor grayColor];
subViewGrid.alpha = 1.0; // make it opaque
[self.schedScrollView addSubview:subViewGrid];
// create a sub-view to hold the appointment DATA
frame = CGRectMake(110,48,670,750);
subViewData = [[UIView alloc] initWithFrame:frame];
subViewData.tag = 22; // use tag to get correct sub-view
subViewData.backgroundColor = [UIColor redColor];
subViewData.alpha = 0.2; // make it sort of transparent
[self.schedScrollView addSubview:subViewData];
[self.subViewGrid setNeedsDisplay]; // **** UPDATED ****
}
Here is the .h file contents for the UIViewController:
#interface CalendarViewController : UIViewController {
UIView *subViewGrid;
UIView *subViewData;
}
#property (strong, nonatomic) IBOutlet UIScrollView *schedScrollView;
- (void) calendarTapNotification:(NSNotification *) notification;
-(NSDate *)beginningOfDay:(NSDate *)date;
-(NSDate *)endOfDay:(NSDate *)date;
#end
In my drawRect method, I have some code that is supposed to draw a "grid" on the subViewGrid. The problem is drawRect never gets called.`
I have read the UIView Programmer's Guide and looked in SO and did a Google search, but found nothing that addresses the issue, which is: why won't [self.subViewGrid setNeedsDisplay] call drawRect from where I have it placed?
Your view controller needs to call setNeedsDisplay for the view it controls, not for itself. So, you want
[self.subViewGrid setNeedsDisplay]
This is just an error in your reading the documentation. Understanding the documentation is critical for objective-C programming so I'll try to help you get a grasp of it.
If you look at the documentation for setNeedsDisplay you will see that it is either a CALayer or UIView class method. If you then look at inheritance, you will see that UIView is UIResponder:NSObject and CALayer is NSObject. None of these inherit from UIViewController which is why you are getting the error. You need to call [self.subViewGrid setNeedsDisplay]

Correct method to programmatically create UIView and UIViewController and link together

I'm new to iOS development. To keep my iOS app nicely compartmentalised I'd like to create both the UIView and the UIViewController programatically, and tie them together once created.
So, I do the following: in my view controller I have this:
-(void)loadView {
NSLog(#"HPSMainMenuViewController loadView starting");
HPSMainMenuView* mainmenuView = [[HPSMainMenuView alloc]initWithFrame:CGRectZero];
self.view = mainmenuView;
}
and in my View I have this:
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"HPSMainMenuView initWithFrame starting");
[self setup];
}
return self;
}
-(void)setup {
UIButton* btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.tag = E_PROFILE_BUTTON;
[btn setTitle:#"Option1" forState:UIControlStateNormal];
[self.view addSubview:btn ];
btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.tag = E_CONTACTS_BUTTON;
[btn setTitle:#"Option2" forState:UIControlStateNormal];
[self.view addSubview:btn ];
self.title = #"Hello";
}
Is this the right way to do this (given I want full programmatic control). It seems wrong to dynamically build the view within the ViewController hence my approach where I am building the view within an actual UIView class.
Lastly, I'm using loadView; should I be using viewDidLoad? If so, why?
Thanks very much.
What you are doing is correct, and actually good. Many developers keep their view controllers and views heavily tied together but if you want to keep them separate then that's great.
loadView is where you should be creating and initializing everything. viewDidLoad can be called multiple times if the view is unloaded/reloaded due to memory warnings (for example). So viewDidLoad is where you would restore saved state, make your view correctly reflect your model, or any other initialization that you can't do in loadView.

How to add UI elements programatically to existing nib files

I wonder how to add UI elements programatically to existing nib files.
If I create a view programatically in loadView method and I add code like the following, the label displays correctly.
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,2,150,100)];
[self.view addView:lbl];
But how to add the label to an existing nib file?
As Paul.s pointed out, you need to perform your custom code in viewDidLoad method.
From Apple documentation.
This method is called after the view controller has loaded its
associated views into memory. This method is called regardless of
whether the views were stored in a nib file or created
programmatically in the loadView method. This method is most commonly
used to perform additional initialization steps on views that are
loaded from nib files.
So, in your controller you could do this:
- (void)viewDidLoad
{
[super viewDidLoad];
// your other views here
// call addSubview method on self.view
}
Why do you do this? Because here you are sure that view has loaded in memory and outlets has been set correctly.
On the contrary, about loadView method
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.
An example could be:
- (void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColor whiteColor];
self.view = contentView;
// call addSubview method on self.view
}
I suggest you to read View Controller Programming Guide for iOS.
Hope it helps.
AFAIK, to modify nib file programmatically is not possible.
You can add view into viewDidLoad of UIViewController method.
Inside viewDidLoad
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,2,150,100)];
[self.view addSubView:lbl];

Resizing UINavigationController?

I want my navigationcontroller to only take half of the screen. Is this possible? In IB, when I drag, it forces me to fill up my entire screen, I can't resize it. If it's not possible, is there an alternative?
Thanks.
You can but you can only do it within iOS 5 because when I tried to do any type of direct view manipulation within a UINavigation controller than pushed or popped another view controller. The navigation controller would not display them.
Here's what you need to do.
-(void)viewDidLoad {
UINavigationController * navController = [[UINavigationController alloc] initWithRootViewController:[[UIViewController alloc] initWithNibName:nil bundle:nil]];
//Used to recieve callbacks (like shouldAutorotateToInterfaceOrientation:)
[self addChildViewController:navController];
//View manipulation
navController.view.frame = CGRectInset(navController.view.frame, 20, 20);
[self.view addSubview:navController.view];
//Calls all the standard methods (viewDidLoad,viewDidUnload,etc.)
[navController didMoveToParentViewController:self];
}
Also if your a registered developer there is a really good video on view controller containers, that might be helpful to you, from last years WWDC.
Based off what you have written I am assuming you have a #property in your header file. Something like:
#property (nonatomic, strong) IBOutlet UINavigationController *navController;
Then have this IBOutlet connected to your navigationController in your xib file. In that same xib file have a view that is connected to the file owner. Set this view's dimensions in interface builder so in your case to fill only half the screen. Then you will add the navController as this view's subview. If you also have a viewController already added in the xib file in the navController then in viewDidLoad do:
-(void)viewDidLoad {
[self addChildViewController:self.navController];
[self.view addSubview:self.navController.view];
[self.navController didMoveToParentViewController:self];
}
Otherwise if you are programmatically setting the rootviewcontroller do:
-(void)viewDidLoad {
self.navController = [[UINavigationController alloc] initWithRootViewController:yourViewController];
[self addChildViewController:self.navController];
[self.view addSubview:self.navController.view];
[self.navController didMoveToParentViewController:self];
}
You have to add self.navController as a childViewController so that it will take on the dimensions of the view it is being added into. Adding it as a subview is not enough to make the view resize. Hope this helps!

UIView transition and animation

I understand modal views cover the entire screen. But I really want a view that covers only half the screen just like the keyboard. So, please tell me why this doesn't work
MyController *controller = [[MyController alloc] initWithNibName:#"MyView" bundle:nil];
CGRect frame = CGRectMake(0,44,768,264);
[controller view].frame = frame;
contoller.delegate = self;
[[self view] addSubView:[controller view]];
[controller release];
I am trying to add a sub view to my current view and make it appear where the keyboard appears.
It throws a BAD ACCESS exception
In my code (above), I was using a custom UIViewController with it's own view [set to UIView on IB]. I couldn't get it to work by setting frame for the view controller's view.
So I added a custom UIView without a Nib file with all the controls (buttons, textfields) added on initWithFrame.
MyCustomView = [[MyCustomView] alloc] initWithFrame:frame delegate:self];
[self.view addSubView:MyCustomView];
Thanks for your comment, Jacob.