Custom UIView subclass as a UIViewController property - iphone

I had thought I actually had a pretty good handle on the whole view controller model, but something just doesn't seem to making much sense for me. My main issue is with adding a custom UIView subclass as a property of a UIViewController subclass.
Whenever I assign a valid instance of a UIView subclass to that property, nothing happens or the code crashes.
Here's a quick outline:
The main controller inits its own view and that loads fine.
I can then add this UIView subclass to the main controller by instantiating it and addSubview:ivar etc. No problems there...
However... if I wanted to have this custom UIView as a property of the ViewController, that does not seem to work. Can anyone shed some light?
Here's a code summary:
#interface CustomUIView : UIView { }
.
#interface MainViewController : UIViewController {
CustomUIView *someOtherView;
}
#property (nonatomic, copy) CustomUIView *someOtherView;
...
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor]; // the default controller view
CustomUIView *tmpView = [[CustomUIView alloc] initWithFrame:CGRectMake(0,0,320,480)];
[self.view addSubview:tmpView]; // this works
self.someOtherView = tmpView; // this does NOT work and
self.view = self.someOtherView; // ultimately, this is what i'm after
[tmpView release];
}
Many thanks to this wonderful community!

You can't copy UIViews. Or UIView subclasses. Try retain, instead.

Related

Storyboard and xib use in same project

I am stumbling over what I believe is probably a fundamental misunderstanding of how classes work in Objective-C. I am using Storyboards but in this app I wanted to create a simple custom date picker view for a textfield on one of my view controllers. However, I seem to be having a problem accessing any of the properties of the date picker class from my view controller:
First I modeled my CustomDatePicker.xib in IB as follows:
I have created a custom class by sublcassing UIView as follows:
CustomPicker.h
#interface CustomPickerView : UIView
#property (strong, nonatomic) IBOutlet UIDatePicker* datePicker;
#end
CustomerPicker.m
#implementation CustomPickerView
- (id)init
{
self = [super initWithFrame:CGRectMake(0, 0, 320, 258)];
if (self) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"CustomPickerView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
In my ViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.customPickerView=[[CustomPickerView alloc] init];
// THE MAIN ISSUE IS...
// Following line has no effect on how picker is presented ???
self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
self.dateField.inputView=self.customPickerView;
}
When the textfield is tapped, my CustomDatePicker pops up fine. However, I can't seem to set the .datePickerMode either from the viewDidLoad method of my ViewController. The only way I can change the .datePickerMode is through IB and of course that's not going to work at run-time.
I have wired up the outlet in IB and am able to access the datePicker.date from within the class but not the ViewController.
I have researched and viewed a number of ways to implement this concept. My question isn't how to implement a CustomDatePicker it is "Why can't I access properties of my CustomDatePicker from the ViewController that instantiated it?
I successfully changed the datePickerMode property when I loaded the NIB like this:
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"CustomPickerView" owner:self options:nil];
self.customPickerView = (CustomPickerView *)[nibArray objectAtIndex:0];
self.customPickerView.datePicker.datePickerMode = UIDatePickerModeTime;
self.dateField.inputView = self.customPickerView;
}
And you can probably remove your custom view's init method altogether. I've never seen a NIB loaded in the init method like you're doing. While it might be possible, I believe that's what is causing your problem. Loading the NIB in viewDidLoad of the ViewController and setting it directly seems more straightforward.
Please see solution here at:
Objective-C Custom Class Actions and Outlets

In Objective-C with no nib file is MVC to have the logic together with the view in same class?

When I work in Objective-C programatically with out nib files, and have the logic in my:
appViewController.m
having in the same class What is going on with that view, as well as with the View elements? Is this against the MVC pattern?
Do I have to create another class and message both classes?
It's up to you! If you want to separate layers (M,V,C) you can create your own view programmatically and, by using composite design pattern, build it in your UIView subclass, by removing drawing code from your controller.
That is...
You create a "CustomCompositeView" that extends UIView
in layoutSubview (hinerited from UIView) you will draw all your UI elements
in your CustomViewController you will display your view using loadView:
code:
- (void)loadView
{
CustomCompositeView *mainView = [[CustomCompositeView alloc] initWithFrame:aFrame];
[self setView:mainView];
[mainView release]; // remove this line if you are using ARC!
}
Technically, it is going against the MVC pattern. Your V and C and combined into a single object. You can seperate the code that handles layout and drawing into a seperate UIView subclass. Then load it with loadView:
// MyViewController.m
- (void)loadView {
MyView* myView = [[[MyView alloc] init] autorelease];
myView.delegate = self;
self.view = myView;
}
#pragma mark - MyViewDelegate Methods
- (void)myViewSaveButtonWasPressed:(MyView *)myView {
// do something
}
To communicate between the view and the view controller, you can define a delegate protocol.
// MyView.h
#class MyView;
#protocol MyViewDelegate <NSObject>
- (void)myViewSaveButtonWasPressed:(MyView *)myView;
#end
#class MyView : NSObject
#property (nonatomic, assign) id<MyViewDelegate> delegate;
// ...
When a button is pressed in the view (or something else along those lines) pass that on to the delegate. The ViewController should conform to the delegate method and handle the actual logic itself that way.

Best Practice: Presenting a Subclassed UIView (with own xib) as subview of UIViewControllers

My goal is, to have a subclassed UIView (lets call it infoView) designed in his own XIB so that I can present it in many UIViewController's.
The Problem:
So far, when I was adding UIView's to a UIViewController I always had to make an UIViewController the file's owner of the UIView's .xib file to load the view with something like:
...
//this is inside the calling UIViewController's method
// InfoView *infoView is ivar and a subclass of UIView
infoView = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"InfoView"
owner:self options:nil];
for (id object in bundle) {
if ([object isKindOfClass:[InfoView class]])
infoView = (InfoView *)object;
}
[[self view] addSubview:infoView];
...
But I want to use the same UIView in many different UIViewController's, so I actually don't want a file's owner except maybe the class itself. In ThomasM's question he was setting the UIView itself to be the file's owner but without success.
In the answers there I found a solution to set the file's owner to nil. To do so I had to add all calling UIViewController objects from the Interface Builder object library to the InfoView.xib file and connect them with their infoView outlets.
But this doesn't feel right. So here I would like to collect solutions to
encapsulate a UIView together with his xib-file to use it in many different view controllers. How do you guys handle that?
Thx for any help.
EDIT:
The infoView is something like an overlay which appears when the user presses a button on one of the view controllers. It's NOT the View controllers "main" view. It gives detailed informations about the view of his superviews view controller and will disappear afterwards. I only fill the infoView with different contents threw out all the calling view controllers.
Like Hollance answer was pointing out I am using UINib.
To use it, leave the .xib files owner nil and place all customization of the infoView inside the initWithCoder: method of your InfoView class implementation. This will get called if you obtain the InfoView.xib like:
// here InfoView is the name of the .xib file
UINib *infoNib = [UINib nibWithNibName:#"InfoView" bundle:nil];
NSArray *topLevelObjects = [infoNib instantiateWithOwner:self options:nil];
QInfoView *infoView = [topLevelObjects objectAtIndex:0];
So you want to load a UIView from a nib that you wish to use in more than one UIViewController, and you want to connect it to an outlet on each of those view controllers. Is that correct?
Then make a UIViewController subclass (let's call it FakeViewController) with an IBOutlet property. Set that FakeViewController as the nib's File's Owner and connect your UIView to its outlet.
Done.
You just need to make sure all your other view controllers also have these outlet properties (although they don't need to be IBOutlets), but the nib loader doesn't actually check to make sure the class that you pass into the owner parameter equals the class name you specified in Interface Builder. So you can fake it.
Oh, and if you're OS 4.0 and higher, use UINib to load the nib file.
And yet another way is to create your own "controller" based on NSObject to define your own life-circle (instead of standard UIViewController life-circle).
For example:
BaseSubview.h:
#interface BaseSubview : NSObject {
UIView* _view;
}
#property (nonatomic, retain) IBOutlet UIView* view;
- (void)myMethod;
#end
BaseSubview.m:
#import "BaseSubview.h"
#implementation BaseSubview
#synthesize view = _view;
- (id)init
{
self = [super init];
if (self) {
// ...
}
return self;
}
- (void)dealloc
{
[_view removeFromSuperView];
[super dealloc];
}
- (void)myMethod
{
// view specific logic here
_view.backgroundColor = [UIColor redColor];
}
#end
InfoView.h:
#import "BaseSubview"
#interface InfoView : BaseSubview {
UILabel* _labelInfo;
}
#property (nonatomic, retain) IBOutlet UILabel* labelInfo;
#end
InfoView.m:
#import "InfoView.h"
#implementation InfoView
#synthesize labelInfo = _labelInfo;
- (id)init
{
self = [super init];
if (self) {
// ...
}
return self;
}
- (void)myMethod
{
// view specific logic here
_labelInfo.text = #"current time...";
[super myMethod];
}
#end
InfoView.xib:
file owner is InfoView
assign of outlets as usual
view is parent all other controls (such as labels, etc)
HugeAndComplicatedViewController.h:
// ...
// among other var definitions
InfoView* _infoView;
// ...
HugeAndComplicatedViewController.m, most interesting part:
// when you decide to show your view
// probably in loadView
_infoView = [[InfoView alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"InfoView" owner:_infoView options:nil];
[self.view addSubview:_infoView.view];
// possibly perform specific logic
[_infoView myMethod];
// no need sub-view any more
// probably in dealloc
[_infoView release];
So now you have your own sub-view with logic and design separated from "Huge & Complicated" view-controller. It can have any life-circle you need for your current project.
does infoView need to be a subview?
in your viewController:
-(id) init {
self = [super initWithNibName:#"myNib" bundle:nil];
if (self) {
// code here
}
}

How to make a .xib show up in a custom class

I was wondering how to connect my custom class and my .xib. In Interface Builder I have changed the class of the Files Owner to my custom class. I then created an IBOutlet and connected it to the view of the .xib. I then added an instance of my custom class to my UIViewController. My custom class is a subclass of UIView, so I set the background of it to black and can see it appear on top of my UIViewController. What I can't see is my .xib???? Here is my code for my custom class... (FreeThrow is my custom class... FetView is my .xib)
#interface FreeThrow : UIView {
IBOutlet UIView *mySquareView;
}
#property(nonatomic, retain)IBOutlet UIView *mySquareView;
-(void)createMe;
#end
#implementation FreeThrow
#synthesize mySquareView;
-(void)createMe {
[self addSubview:mySquareView];
[self setBackgroundColor:[UIColor blackColor]]; // I did this to know my UIViewController is showing this class... which it is
}
#end
Here is my code for what I call from my UIViewController...
freeThrowView = [[FreeThrow alloc] init];
[freeThrowView createMe];
freeThrowView.frame = CGRectMake(8, 42, 335, 230);
[self addSubview:freeThrowView];
What code do I need to add? What do I need to do? What is the problem? Why is my .xib not showing up.
Sadly apple doesn't give us a way to truly link a xib to a view via IB alone. You'll have to do it via instantiation. Here are some past answers to similar questions:
How do I associate a nib (.xib) file with a UIView?
Loading .xibs into a UIView

UIView and UIViewController practice for inheritance

I was just wondering if this approach looks like a good practice for apps with a lot of custom views, for nested PNG graphics and animations that may change based on user interaction. I created a BaseView class that extends UIView
#interface BaseView : UIView {
#protected
BaseViewController *controller;
}
#property (retain) BaseViewController *controller;
#end
and a corresponding controller class which is the primary location which I am putting code to manipulate the view
#interface BaseViewController : UIViewController {
#protected
CGRect drawArea;
}
- (void) drawArea:(CGRect) _drawArea;
- (CGRect) drawArea;
- (void) linkSubviewController: (BaseViewController *) _subviewController;
#end
where "drawArea" is the CGRect used to pass to the view as a frame.
"linkSubviewController" allows you to nest a controller and view as follows :
- (void) linkSubviewController: (BaseViewController *) _subviewController {
[self.view addSubview:[_subviewController view]];
}
In addition I layered another custom pair called "ImageView" and "ImageViewController" which extend BaseView but also store a UIImage and an x,y,w,h
In the "drawRect" drawing methods on views I can check to see if any vars in the self.controller vars have been changed, or assign images, for example :
UIImage *image = [(ImageViewController *)self.controller image];
CGContextDrawImage( ... ) etc
I write most of the loadView methods something like this
- (void)loadView {
ImageView *v = [[ImageView new] initWithFrame:drawArea];
v.controller = self;
self.view = v;
}
The base "initWithFrame" routine contains
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
So I can load a variety of images with transparent backgrounds without having to assign that each time.
I've been able to use this code throughout my app and it seems to make it easy to write a top level class which assembles the layout of custom items. For animations I've been putting them in the controllers and manipulating self.view.layer.
Basically I am looking for feedback, I am new with Objective-C and the IPhone SDK
There are several issues here:
Using [[Classname new] init...] is incorrect usage of new. Using new is short for [[Classname alloc] init] so you are effectively calling init twice.
Views shouldn't really need to know who is controlling them.
Your view is retaining the controller, and since UIViewController retains its view, you have a retain cycle and neither will ever be fully released.
If you want this type of behavior (whereby a view can delegate its drawing to a parent), try creating a DrawDelegate protocol, have your controllers implement that protocol, and in your view subclass have a non-retaining drawDelegate property:
#protocol DrawDelegate
- (void) drawArea:(CGRect)rect;
#end
#interface BaseView : UIView {
id<DrawDelegate> drawDelegate;
}
#property (assign) id<DrawDelegate> drawDelegate;
#end