How to display a view from a NSViewController? - objective-c

I'm new at objective-c so please bear with me. I have a class that returns a picture from a webcam and I am trying to display that to the screen. I subclassed NSViewController to get the image from the camera class and set it to an instance NSImageView and set the NSViewController's view to be the NSImageView. I have created a custom view in Interface Builder, dragged a NSViewController object into MainMenu.xib set it's class to be PhotoGrabberController, and control click-dragged from the custom view to the PhotoGrabberController to set it's outlet binding to be the view. (I really don't know how that works behind the scenes, it seems like magic to me). Nothing shows up on the screen and I've been playing around with this forever.
In PhotoGrabberController.h
#interface PhotoGrabberController : NSViewController {
PhotoGrabber * grabber;
NSImageView* iView;
}
#property (nonatomic, retain) NSImageView* iView;
In PhotoGrabberController.m
#implementation PhotoGrabberController
#synthesize iView,grabber;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void) awakeFromNib
{
self = [super init];
if (self) {
NSRect rect = NSMakeRect(10, 10, 200, 100);
iView = [[NSImageView alloc] initWithFrame:rect];
[iView setImageScaling:NSScaleToFit];
[iView setImage:[NSImage imageNamed:#"picture.png"]];
grabber = [[PhotoGrabber alloc] init];
grabber.delegate = (id)self;
//[grabber grabPhoto]; (This usually sets iView but I removed it and manually set iView to be an image from file above for simplicity)
[self setView:iView];
}
}
I toyed around with putting a bunch of different things in the AppDelegate's applicationDidFinishLaunching but the way it's set up right now I think I shouldn't need to put anything in there.
Question
How do I get the main window to show the image?
Is it ok to use PhotoGrabberController.view instead of creating a view subclass?
What is wrong with my understanding of how things work here? I've spent a lot of time trying to figure this out and have gotten nowhere.
Can you direct me to a resource where I can fully understand how this works? I've found Apple documentation too detailed and thick. I just want to understand how windows, views, and view controllers are loaded and interact.

How do I get the main window to show the image?
That's a view's job.
As you already have a view, you just need to get it into the window. A window only holds one view directly, which is called its content view; most probably, you will want to add the view controller's view as a subview of the view that is already the content view, or as a subview of some other grandchild view.
Is it ok to use PhotoGrabberController.view instead of creating a view subclass?
These are orthogonal. The VC's view is an instance of a view class. Creating a class has nothing to do with using an instance.
I don't think creating a view subclass will help you here. You already have two working views: a plain NSView as the VC's immediate view, and a NSImageView within it. No subclass is necessary to add these to the view—with the image view within it—to the window.
What is wrong with my understanding of how things work here?
You've missed the entire concept of the view hierarchy, and the fact that such a hierarchy is how every window displays its content.
Also, you began your question with “I'm new at objective-c” (sic), but your question is about the Cocoa framework (specifically, the AppKit framework), not the Objective-C language.
Can you direct me to a resource where I can fully understand how this works?
The Window Programming Guide (particularly, “How Windows Work”) and the View Programming Guide (particularly, “Working with the View Hierarchy”).

Related

Xcode: How to add Custom View from XIB File in a UIScrollView

Coming from an Android and Java background, I am relative new with Xcode Development.
I am using Xcode 5.0.2 and created my first IOS Application by selecting Create a New Xcode Project -> Single View Application. The initial project structure has been generated and I found that there is a Main_iphone.storyboard file which opens a UI Designer where I can drag and drop items to it. I selected UIScrollView and dragged it into the main window that has been generated.
Now in the Controller header file, I added #property (nonatomic, retain) IBOutlet UIScrollView *scrollView; so that i could access the scrollView from inside my controller code.
I wanted to add an item to the scrollView programatically so I created a template Custom View by adding new file -> Objective-C Class with XIB for User Interface, named it TSTFilesInfoController and designed the XIB by adding a View and a label inside the view. Same with the scrollView above, I created a property to expose the mainView in my controller class.
I hardcoded a loop of 10x inside the controller of the UIScrollView and inside the for loop I am instantiating TSTFilesInfoController and adding the view to the UIScrollView. But when i run the application, nothing is shown or added in the UIScrollView.
Heres the code for adding the CustomView:
- (void)viewDidLoad {
[super viewDidLoad];
for ( int i = 0; i < 10 ; i++) {
TSTFilesInfoController *info = [[TSTFilesInfoController alloc] init];
[self addChildViewController:info];
[self.scrollView addSubview:info.mainView];
NSLog(#"View has been added into the scrollView");
}
}
Can someone please tell me whats wrong with my codes and what would be the correct approach to achieve the output that i wanted? Thank you in advance for the help.
-- EDIT --
This code is auto-generated in TSTFilesInfoController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
This is a misuse of a UIViewController subclass. Do not use a view controller just as a sort of fishing rod to hook a view that's inside a xib file. Simply load the xib file and grab the view yourself, and stuff it into your interface.
There are complex rules for how to put a view controller's view manually inside your interface, and in general it is something you should be reluctant to do. But making views come and go dynamically and directly is easy and common.
Let's suppose the .xib file is called TSTFilesInfo.xib and it has a top-level UIView subclass object, class MyView, which is the one you want. Then:
MyView* v = (MyView*)
([[UINib nibWithNibName:#"TSTFilesInfo" bundle:nil]
instantiateWithOwner:nil options:nil][0]);
This loads the nib once, instantiating its contents, and handing you a reference to that instance (the UIView in this case). Now plunk v into your interface. Keep that reference in an instance variable (probably weak, since it is retained by its superview) so that you can subsequently configure and communicate with any subviews of MyView to which you have created outlets.
NOTE: However, I must say from the example so far that it sounds to me like what you really want here is a UITableView, not a simple UIScrollView. It comes all set to do just the kind of thing you seem to up to here.

Adding a header view to every view in an application

I have a header that I would like to add to every view in my program. Rather than manually do this I've been trying to find a way to insert this "header view" above every view that gets loaded. In the sample below, I tried using the insertSubView method but this inserts the entire view and not just the tiny bit of header content I'm interested in.
[self.view insertSubview:self.headerController.view aboveSubview:self.indexController.view];
[self.view insertSubview:self.indexController.view atIndex:0];
Does anyone know how to do this correctly? I'm using Xcode 5.
You're inserting the views of the headerController and indexController on your other view controllers' views. Generally, though, you want to be careful about keeping your view hierarchy synchronized with your view controller hierarchy. The importance of this is discussed in some detail in WWDC 2011 video Implementing UIViewController Containment.
So, generally you'd instantiate the appropriate indexController and headerController, call addChildViewController, add the subviews, and then call didMoveToParentViewController for each of those two view controllers. For more information, see Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS. But you app's view controllers might do something like:
HeaderController *headerController = ... // instantiate however is appropriate
[self addChildViewController:headerController];
headerController.view.frame = [self frameForHeaderController]; // define the frame/constraints as appropriate
[self.view addSubview:headerController];
[headerController didMoveToParentViewController:self];
You'd then repeat that process for the indexController.
Just make sure to (in addition to doing the custom container calls) either set the frame of the view controller's frame or define auto-layout constraints that will dictate the placement of the view (like you do for any programmatically added view). If you don't specify the frame, it may well end up being CGRectZero, which is obviously not what you intended.
You might, though, consider flipping this around. Make a custom container view controller that includes your header and any other UI elements that persist for every view. Then make your app's view controllers as child controllers of that container "parent" view controller. If either the header controller's view or the index controller's view navigates your app between various content view controllers, this sort of structure might make more sense.
I think if you watch that WWDC video and/or review the Creating Custom Container View Controllers, this will make more sense.
I'd suggest to create a base view from which you will subclass for every views that need this header.
You could have a BaseView.h that would look like:
#interface BaseView : UIView
#property (nonatomic) UIView *someHeaderView;
#end
And the implementation (.m) looking like this:
#implementation BaseView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.headerView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(frame), 150.0)];
[self.view addSubview:self.headerView];
}
return self;
}
#end
Then have every views that requires a header subclass it:
#interface SomeViewRequiringHeader : BaseView
#end
Try reversing the order of the two:
[self.view insertSubview:self.indexController.view atIndex:0];
[self.view insertSubview:self.headerController.view aboveSubview:self.indexController.view];
Also, since headerController is a UIViewController, it probably has a nib file with a default size of full screen. If you want to only be a certain height, you have to modify that nib file with the appropriate height.

Change self.view's class type

I have a app out for testing right now that's almost completely done - just a few bug fixes left. Unfortunately, the customer decided that they'd like the entire main page of the app to be inside of a scroll view (so that there's more room for the table at the bottom). I already have everything set up and I don't really want to move everything and change references. Is there an easy way to change the class of the main view to a scroll view? I've already tried changing the class in IB and setting the class type in the init method. If there isn't I'll probably just throw the top section of the view into a nib file and load it as a custom cell.
-EDIT- I ended up changing the class type in IB and then doing
[(UIScrollView *) self.view setScrollEnabled:YES];
[(UIScrollView *) self.view setContentSize:CGSizeMake(0,2000)];
in viewDidLoad. Thanks for the help, wish I could accept all your answers.
When you are referring to [self view], I am going to assume you mean in a view controller. The view of a view controller can be any view that derives from UIView. Thus a scrollview is completely acceptable.
I don't really want to move everything and change references.
what would you have to move? why would you have to change references? Only thing you should need to do is add a scroll view to your view controller, set the view controllers view to it, and add the current view as a subview to the new scroll view. No references need to be changed, nothing has to be moved.
Refer to loadView method in documentation of view controller.
Here is a simple (untested!) example
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init] autorelease];
//Set the properties of scrollview appropriately....
self.view = scrollView;
}
Now the root view of your view controller will be a scroll view.
Note
- As the documentation states, do not do this if you are using interface builder to initialize your views/view controller. I could not tell from your description if this was the case or not. If it is, you should be able to change the view type in interface builder.
You need to set the contentSize property of your scrollview.
Since you are using IB, the easiest way to do this is to put all your UI elements into a view and add this single view to your scroll view. In the viewDidLoad method, set the content size of the scrollview to be the same size as the view that contains all your UI.
As an aside, there are much easier ways to reference views than walking down the view hierarchy, as you seem to be doing. viewcontroller.view.something.tableview. Add a connection to the tableview from your view controller in IB and it doesn't matter where that tableview is in the view hierarchy. You'll always be able to reach it from viewcontroller.tableview, no matter how you rearrange your nibs.
I think you have to use a pointer with proper type. Example for Google Maps: let's say you changed you base view's class to GMSMapView.
MapViewController.h
#property GMSMapView *mapView;
MapViewController.m
-(void)awakeFromNib{
[super awakeFromNib];
self.mapView = (GMSMapView*)self.view;
// ... etc.
}

How can I make a common control in Objective-C that can be used in multiple views in a storyboard?

I am just starting in my iOS development using xCode 4.2 and discovered storyboards. They seem great for rapid prototyping.
What I'm wondering is how can I create my own custom control that I can use in multiple views.
For example, let's say I want a custom title bar that is common on every view. I think I should be able to define that control with it's own controller and instantiate that from multiple views. I just don't know how and the tutorials and questions I've read so far don't address this.
Any tips?
I've found one way to do this is to do the following steps:
Create a new xib file and set the Simulated Metrics to "Freeform" to allow resizing. (MyControl.xib)
Populate the control with the Objects I want in the control.
Create a UIViewController for the view. (MyViewController.h & MyViewController.m)
Set the File's Owner of the MyControl.xib to the Custom Class MyViewController
In the xib that I want to include the control, I put a UIScrollView where I want the control (a regular View will work too). (Parent.xib)
Create an IBOutlet for the UISCrollView that I will put the control into in the ParentController.
Create an instance of MyViewController in the ParentController.
In the ParentController, add the view of the MyViewController as a subview of the UIScrollView.
In code, this means
#implementation ParentController
#synthesize myScrollView;
MyViewController* myController;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super init];
if(self)
{
myController = [[MyController alloc] initWithNibName:#"MyView" bundle:nil];
[myScrollView addSubview:myController.view];
}
}
This seems to work and allows me to separate the implementation of the Control and the Parent, but I can't help but think there is a better way.

iPhone subview design (UIView vs UIViewController)

I'm designing a simple Quiz application. The application needs to display different types of QuizQuestions. Each type of QuizQuestion has a distinct behavior and UI.
The user interface will be something like this:
alt text http://dl.getdropbox.com/u/907284/Picture%201.png
I would like to be able to design each type of QuizQuestion in Interface Builder.
For example, a MultipleChoiceQuizQuestion would look like this:
alt text http://dl.getdropbox.com/u/907284/Picture%202.png
Originally, I planned to make the QuizQuestion class a UIViewController. However, I read in the Apple documentation that UIViewControllers should only be used to display an entire page.
Therefore, I made my QuizController (which manages the entire screen e.g. prev/next buttons) a UIViewController and my QuizQuestion class a subclass of UIView.
However, to load this UIView (created in IB), I must[1] do the following in my constructor:
//MultipleQuizQuestion.m
+(id)createInstance {
UIViewController *useless = [[UIViewController alloc] initWithNibName:#"MultipleQuizQuestion" bundle:nil];
UIView *view = [[useless.view retain] autorelease];
[useless release];
return view; // probably has a memory leak or something
}
This type of access does not seem to be standard or object-oriented. Is this type of code normal/acceptable? Or did I make a poor choice somewhere in my design?
Thankyou,
edit (for clarity): I'd like to have a separate class to control the multipleChoiceView...like a ViewController but apparently that's only for entire windows. Maybe I should make a MultipleChoiceViewManager (not controller!) and set the File's Owner to that instead?
You're on the right track. In your QuizController xib, you can create separate views by dragging them to the xib's main window rather than to the QuizController's main view. Then you can design each view you need according to your question types. When the user taps next or previous, remove the previous view and load the view you need based on your question type using -addSubview on the view controller's main view and keep track of which subview is currently showing. Trying something like this:
[currentView removeFromSuperView];
switch(questionType)
{
case kMultipleChoice:
[[self view] addSubview:multipleChoiceView];
currentView = multipleChoiceView;
break;
case kOpenEnded:
[[self view] addSubview:openEndedView];
currentView = openEndedView;
break;
// etc.
}
Where multipleChoice view and openEndedView are UIView outlets in your QuizController connected to the views you designed in IB. You may need to mess with the position of your view within the parent view before you add it to get it to display in the right place, but you can do this with calls to -setBounds/-setFrame and/or -setCenter on the UIView.
Yeah, IB on iPhone really wants File's Owner to be a UIViewController subclass, which makes what you want to a bit tricky. What you can do is load the nib against an existing UIViewController instead of instantiating one using the nib:
#implementation QuizController
- (void) loadCustomViewFromNib:(NSString *)viewNibName {
(void)[[NSBundle mainBundle] loadNibNamed:viewNibName owner:self options:nil];
}
#end
That will cause the runtime to load the nib, but rather than creating a new view controller to connect the actions and outlets it will use what you pass in as owner. Since we pass self in the view defined in that nib will be attached to whatever IBOutlet you have it assigned to after the call.