Programmatic creation of NSView in Cocoa - ios

i'm used to programming for iOS, and I've become very accustomed to the UIViewController. Now, i'm creating an OSX application and i'm having a few general questions on best practice.
In a UIViewController I generally setup my views in the -(void)viewDidLoad method - I don't actually create a custom UIView for the UIViewController unless it's really needed - so the UIViewController adds view to its own view, removes them, animates them and so forth - first off, is good practice?
And for my main question - what is the best practice in OSX? I like creating interfaces programatically and simply prefer it that way. If i, say create a new custom window and want to manage its view. What's the best way to do it, and where to i instantiate the user interface best?
Summary: How do i construct custom views programatically and set up a best-practice relationship between views and controllers in OSX? And is it considered good practice to use a view controller to create the views within its view?
Kind regards

To construct the view in code in an NSViewController, override loadView and be sure to set the view variable. Do not call super's implementation as it will attempt to load a nib from the nibName and nibBundle properties of the NSViewController.
-(void)loadView
{
self.view = [[NSView alloc] init];
//Add buttons, fields, tables, whatnot
}
For a NSWindowController, the procedure is very similar. You should call windowDidLoad at the end of your implementation of loadWindow. Also the window controller does not call loadWindow if the window is nil, so you will need to invoke it during init. NSWindowController seems to assume you will create the window in code before creating the controller except when loading from a nib.
- (id)initWithDocument:(FFDocument *)document
url:(NSURL *)url
{
self = [super init];
if (self)
{
[self loadWindow];
}
return self;
}
- (void)loadWindow
{
self.window = [[NSWindow alloc] init];
//Content view comes from a view controller
MyViewController * viewController = [[MyViewController alloc] init];
[self.window setContentView:viewController.view];
//Your viewController variable is about to go out of scope at this point. You may want to create a property in the WindowController to store it.
[self windowDidLoad];
}
Some optional fancification (10.9 and earlier)
Prior to 10.10, NSViewControllers were not in the first responder chain in OSX. The menu will automatically enable/disable menu items for you when an item is present in the responder chain. You may want to create your own subclass of NSView with an NSViewController property to allow it to add the controller to the responder chain.
-(void)setViewController:(NSViewController *)newController
{
if (viewController)
{
NSResponder *controllerNextResponder = [viewController nextResponder];
[super setNextResponder:controllerNextResponder];
[viewController setNextResponder:nil];
}
viewController = newController;
if (newController)
{
NSResponder *ownNextResponder = [self nextResponder];
[super setNextResponder: viewController];
[viewController setNextResponder:ownNextResponder];
}
}
- (void)setNextResponder:(NSResponder *)newNextResponder
{
if (viewController)
{
[viewController setNextResponder:newNextResponder];
return;
}
[super setNextResponder:newNextResponder];
}
Finally, I use a custom NSViewController that overrides setView to set the viewController property when I use my custom views.
-(void)setView:(NSView *)view
{
[super setView:view];
SEL setViewController = #selector(setViewController:);
if ([view respondsToSelector:setViewController])
{
[view performSelector:setViewController withObject:self];
}
}
- (BOOL)acceptsFirstResponder
{
return YES;
}

And for my main question - what is the best practice in OSX? I like
creating interfaces programatically and simply prefer it that way. If
i, say create a new custom window and want to manage its view. What's
the best way to do it, and where to i instantiate the user interface
best?
All these are done in awakeFromNib and init.
The following code is creating many windows and storing them in array. For each window you can add views. And each view may contains all the controls you wish to have.
self.myWindow= [[NSWindow alloc] initWithContentRect:NSMakeRect(100,100,300,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.myWindowArray addObject:self.myWindow];
for (NSWindow *win in self.myWindowArray) {
[win makeKeyAndOrderFront:nil];
}

Related

UIView subclass in XIB doesn't display (drawRect: never called)

I am designing a custom view controller in interface builder with a XIB file and I have a custom UIView subclass that I want to add to my view controller. Here is how I've put my custom view into the view controller:
My TonerEffectButtonView class is a subclass of UIView (and has nothing to do with UIButton) and here is my code for it:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)prepareWithSelector:(SEL)selector onTarget:(id)targ withFilter:(GPUImageFilter*)filter{
self.gpuImageView = [[GPUImageView alloc] initWithFrame:self.frame];
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:targ action:selector];
[self addGestureRecognizer:gesture];
selectedFilter = filter;
[filter addTarget:self.gpuImageView];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
}
initWithFrame: is not called anyway as I'm creating the view through a XIB, and I don't need any customization in initWithCoder: so I haven't implemented it. I have a GPUImageView (that is a part of a library that I'm using) that I'm creating programatically inside my view, and adding camera input to it (if you ask what happens in the prepare method). Prepare method is called from outside, and I've verified my UIView's frame is correct when it is called. I have a breakpoint on [super drawRect:rect]; and it never gets called, and when I run the program, my custom view is not displayed, nor does receive touch events (but prepare method DOES get called so the instance is created with the correct frame). It's like it has never been put there in interface builder. What could be the cause of this? I've seen many posts about this, and they mostly refer to creating views programatically and indicating problems with 'initWithFrame:'. But I have nothing to do with that method, and I want to use the interface builder/XIB couple, please don't advice me to create the view controller programatically. What could be the cause of it?
Thanks,
Can.
UPDATE: I can verify that -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx is also not called in any way.
Ok, I've found the answer. First, I've tried to add another instance programatically, and I've found out that it has autoresizing mask set to none, whereas my original instance has it set to W+H. This clue made me realize that in fact, I was forgetting to add my GPUImageView instance as a subview into my custom view. I've added it, and it worked.

iOS: Dynamic Creation of UISegmentedControl within UIView

I am wanting to create a custom UIView class that will show a dynamic number of UISegmentedControl objects depending on some input. For example, if a client has 5 products in their cart, the UIView should generate 5 UISegmentedControl objects that I will then link with each item.
The problem I am having is getting this to work in a UIView. Here is what I have done so far. I am successfully able to create a UISegmentedControl object and display it programmatically within my main UIViewController. I don't get any display when adding it to my UIView class. Here is the implementation code for the UIView class:
#import "ajdSegmentView.h"
#implementation ajdSegmentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *itemArray = [NSArray arrayWithObjects:#"Yes", #"No", nil];
UISegmentedControl *button = [[UISegmentedControl alloc] initWithItems:itemArray];
button.frame = CGRectMake(35,44, 120,44);
button.segmentedControlStyle = UISegmentedControlStylePlain;
button.selectedSegmentIndex = 1;
[self addSubview:button];
}
return self;
}
#end
I created a new UIView object via Storyboard and placed it inside the UIViewController scene. I made sure to set the class from the generic UIView class to my new custom class. I added and outlet for the UIView in my UIViewController class. Here is the code inside the implementation of UIViewController:
#import "ajdViewController.h"
#interface ajdViewController ()
#end
#implementation ajdViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.segmentView = [[ajdSegmentView alloc] init];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
That's all I have tried. I have been searching through a lot of pages and trying to implement this without asking here, but I seem to be looking in the wrong places.
First you need to check ajdSegmentView is UIVIew or UIViewController. It is fine if it is UIView. If it is type of UIViewController then you need to add this line while adding Segment.
[self.view addSubview:button];
In place of:
[self addSubview:button];
And One more thing You forget to add this View to your main after allocating so You can declare like this:
objajdSegmentView = [[ajdSegmentView alloc] init];
[self.view addSubview:objajdSegmentView.view];
I have just added this thing. i got result like this way.
Hope this will work for you.
You're initializing your custom view using the init method, but your initialization for ajdSegmentView is in your initWithFrame: method (which in your case is not getting called).
So replace:
self.segmentView = [[ajdSegmentView alloc] init];
with:
// Change the frame to what you want
self.segmentView = [[ajdSegmentView alloc] initWithFrame:CGRectMake(0,0,100,40)];
Also don't forget to add your view to the view controller's view also.
[self.view addSubview:self.segmentView];
Unless this view is being created with interface builder, in which case you will need to override initWithCoder: in your ajdSegmentView class.
I'm not familiar with Storyboard though, so maybe I'm missing something, but in a standard scenario what I said above will solve your problem.

xcode storyboard Container View - How do I access the viewcontroller

I'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.
I have my story board setup as:
And its referenced in my .h file as:
Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.
There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:
The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.
Update 2013-01-17 - Example
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
// -- Master View Controller
if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
{
self.masterViewController = segue.destinationViewController;
// ...
}
// -- Detail View Controller
else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
{
self.detailViewController = segue.destinationViewController;
// ...
}
}
c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.
When you add a container view the xcode calls the UIViewController method addChildViewController:
In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:
for (UIViewController *childViewController in [self childViewControllers])
{
if ([childViewController isKindOfClass:[InstallViewController class]])
{
//found container view controller
InstallViewController *installViewController = (InstallViewController *)childViewController;
//do something with your container view viewcontroller
break;
}
}
I had the same doubt yesterday :)
The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.
Create a readonly property
#interface MyViewController ()
#property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
#end
Then create a convenient getter method
- (InstallViewController *)installViewController
{
if (_cachedInstallViewController) return _cachedInstallViewController;
__block InstallViewController *blockInstallViewController = nil;
NSArray *childViewControllers = self.childViewControllers;
[childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {
if ([childViewController isMemberOfClass:InstallViewController.class])
{
blockInstallViewController = childViewController;
*stop = YES;
}
}];
_cachedInstallViewController = blockInstallViewController;
return _cachedInstallViewController;
}
From now on access the child view controller that way
[self.installViewController doSomething];
UIView* viewInsideOfContainer = installerView.subviews[0];
Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.
If the nib is loaded it will call addChildViewController as part of the initialisation process
so a performant solution could be also to overwrite
- (void)addChildViewController:(UIViewController *)childController
there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar
-(void)addChildViewController:(UIViewController *)childController
{
[super addChildViewController:childController];
if([childController isKindOfClass:[InstallViewController class]])
{
self.installViewController = (InstallViewController *)childController;
}
}
This will save your from iterating trough the childViewControllers.

Integration of QuickDialog with Storyboard

I am a new to iOS development, and I recently came across QuickDialog. From what it seems, it creates the dialog page for you automatically.
My recent learning has taught me to use the Storyboard to create the views. I was wondering, would QuickDialog integrate with Storyboard? So say I had a login form made by quick dialog, would the login view appear on the storyboard?
Thanks!
Your link seems broken. But I guess you are talking about this library?
Well, storyboard doesn't change much of the development environment. It just handles some transition between view controllers for you. So, yes, you can use QuickDialog with storyboard.
But it will not just appear in your storyboard. You need to add view controllers implemented with QuickDialog in it by yourself.
You have to create the QRootElement when storyboard is creating the controller in initWithCoder, and if you want to use the grouped option, it has to be set there, for the rest of the options, you can set them in the viewload, apart from that it's as usual:
Create a new class inheriting from QuickDialogController
Add in your class implementation the following code:
-(id) initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
QRootElement *_root = [[QRootElement alloc] init];
_root.grouped = YES;
/* Put your init code here or in viewDidLoad */
self.root = _root;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
QSection *section = [[QSection alloc] init];
QLabelElement *label = [[QLabelElement alloc] initWithTitle:#"Hello" Value:#"world!"];
[section addElement:label];
self.root.title = #"Hello World";
[self.root addSection:section];
}
Set the custom class of your storyboard UIViewController to be the one you just created

When do I need to call -[UIViewController initWithNibName:bundle:]?

In post Using initWithNibName changes absolutely nothing, he shows two uses of the same View Nib definition, in the first case, he simply calls alloc/init and the second, he specifies initWithNibName.
So, while this always works:
MyViewController *vctrlr = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:vctrlr animated:YES];
[vctrlr release];
The following works for all the View Controllers I've inherited, but not mine!
TheirViewController *vctrlr = [[TheirViewController alloc] init];
[self.navigationController pushViewController:vctrlr animated:YES];
[vctrlr release];
New to iOS programming, I inherited some code. All the View Controllers' views are defined in IB, but there was inconsistent allocation/init creation of those view controllers. I created a new View Controller and XIB, but it does not work unless I use initWithNibName (it crashes when I push the view controller onto the Nav Controller). I cannot tell how my view controller is different than the others... any hints? I was able to delete the initNibName usage for all the other view controllers in the app except mine.
You can pass any string name to initWithNibName:. You are not just restricted to calling initWithNibName:#"MyClassName" when your class is called MyClassName. It could be initWithNibName:#"MyClassNameAlternateLayout".
This becomes useful if you need to load a different nib depending on what the app needs to do. While I try to have one nib per view controller per device category (iPhone or iPad) whenever possible to make development and maintenance simpler, I could understand if a developer would want to provide a different layout or different functionality at times.
Another important point is that initWithNibName:bundle: is the designated initializer for UIViewController. When you call -[[UIViewController alloc] init], then initWithNibName:bundle: is called behind the scenes. You can verify this with a symbolic breakpoint. In other words, if you simply want the default behavior, it is expected that you can call -[[UIViewController alloc] init] and the designated initializer will be called implicitly.
If, however, you are calling -[[UIViewController alloc] init] and not getting the expected behavior, it's likely that your UIViewController subclass has implemented - (id)init incorrectly. The implementation should look like one of these two examples:
- (id)init
{
self = [super init];
if (self) {
// custom initialization
}
return self;
}
or
- (id)init
{
NSString *aNibName = #"WhateverYouWant";
NSBundle *aBundle = [NSBundle mainBundle]; // or whatever bundle you want
self = [self initWithNibName:aNibName bundle:aBundle];
if (self) {
// custom initialization
}
return self;
}
If you want to work following code:
MyViewController *vctrlr = [[MyViewController alloc] inil];
[self.navigationController pushViewController:vctrlr animated:YES];
Then you should implement following both methods in MyViewController:
- (id)init
{
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self != nil)
{
// Do initialization if needed
}
return self;
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
NSAssert(NO, #"Init with nib");
return nil;
}

Resources