heres my conundrum
i am try to put a table view above my view controller
the view loads and is added, the array that i am using for the tableview is always null however so the table is always empty!
here is my view header file with my tableview and mutable array properties
#interface IngredientsView : UIView <UITableViewDataSource,UITableViewDelegate>
- (id)initWithFrame:(CGRect)frame andIngredients:(NSMutableArray*)ingredientsArray;
- (id)initWithIngredients:(NSMutableArray*)ingredientsArray;
#property (weak, nonatomic) IBOutlet UITableView *notesTableView;
#property NSMutableArray* ingredients;
my custom init method takes in the array and sets it - here is my custom init
- (id)initWithIngredients:(NSMutableArray*)ingredientsArray;
{
self.ingredients = [[NSMutableArray alloc]init];
self.ingredients=ingredientsArray;
NSLog(#"hello from init %lu", (unsigned long)[self.ingredients count]);
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", [self class]] owner:self options:nil] objectAtIndex:0];
UIImageView* view = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"ingredients background.png"]];
self.notesTableView.backgroundView=view;
self.backgroundColor = [UIColor colorWithRed:0/255.0f green:0/255.0f blue:0/255.0f alpha:0.6f];
self.notesTableView.dataSource = self;
self.notesTableView.delegate = self;
UIGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(close)];
[self addGestureRecognizer:tapGesture];
[self.notesTableView reloadData];
}
return self;
}
inside the init method the self.ingredients array will return a count (e.g. 6 objects) to NSlog
but in my delegate table view method numberOfSectionsFor:index path it always returns 0 as if the array is nil
i have tried
using a instance array instead
synthesising the array and setting it from my view controller
sticking the array on a singleton and grabbing the array from a singleton
all return 0 for number of sections ?
is the tableview nil?
i normally use table view controllers and don't have much experience using just tableviews is there something i have missed?
your setting self.ingredients=ingredientsArray; and then on the following line setting self to a nib that you your loading, overwriting self. Move the self.ingredients assignment to after you set self to the nib.
Which of your initializers are called? If you are being loaded from an xib file, certainly
-(id) initWithFrame:(CGRect)frame andIngredients:
is not called
- (id) initWithFrame: is the designated initializer for views. so it will be called when loading views from a nib file.
In any case , the first thing your initWith... code should do is:
self = [super initWithFrame: ... ]
if (self)
{
...
}
The MOST critical problem:
You are assigning self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", [self class]] owner:self options:nil] objectAtIndex:0];
You just blasted over self which is being initialized.
If you were wanting IngredientsView to be loaded from the XIB file, you might also consider implementing -(void) awakeFromNib {...} to set your properties, or set the properties in the - (self) InitWithFrame: .... after calling[super initWIthFrame:rect]`
Related
So the question is like this:
I subclass 2 classes, which is UIView(name: Menu) and UIViewController(name: MainController). In Menu, i used xib file to create it layout. In MainController, i added Menu as subview like this and comform to protocol in Menu.
SliderMenu *sliderMenu = [[[NSBundle mainBundle] loadNibNamed:#"SliderMenu" owner:self options:nil] objectAtIndex:0];
sliderMenu.datasource = self;
sliderMenu.delegate = self;
[self.view addSubview:sliderMenu];
The layout works perfectly, there is no problem with it.
The problem is with DataSource. i call a datasource method in awakeFromNib
- (void)awakeFromNib {
// Alloc data
self.data = [[NSArray alloc] initWithArray:[self.datasource arrayOfData]];
}
and that never even get called. After trying and trying i found out that, sliderMenu.datasource = self;is run after awakeFromNib. Thats is why the datasource method in MainController is never get called.
Question:
How can i solve this problem?
If you put a breakpoint in -awakeFromNib method, you'll see that this method executes as it should.
The thing is, that this method called before datasource assignment, and at this moment your self.datasource is nil.
I suggest you to override the setter of the datasource property and initialize your data there, e.g.
- (void)setDatasource:(id<YourProtocol>)datasource
{
_datasource = datasource;
self.data = [[NSArray alloc] initWithArray:[datasource arrayOfData]];
}
OR
make a public method, say, prepare and do all your initialization there,
- (void)prepare
{
self.data = [[NSArray alloc] initWithArray:[self.datasource arrayOfData]];
}
and call this method after datasource assignment:
SliderMenu *sliderMenu = [[[NSBundle mainBundle] loadNibNamed:#"SliderMenu" owner:self options:nil] objectAtIndex:0];
sliderMenu.datasource = self;
sliderMenu.delegate = self;
[sliderMenu prepare];
[self.view addSubview:sliderMenu];
OK, there are dozens of posts on StackOverflow about this, but none are particularly clear on the solution. I'd like to create a custom UIView with an accompanying xib file. The requirements are:
No separate UIViewController – a completely self-contained class
Outlets in the class to allow me to set/get properties of the view
My current approach to doing this is:
Override -(id)initWithFrame:
-(id)initWithFrame:(CGRect)frame {
self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] objectAtIndex:0];
self.frame = frame;
return self;
}
Instantiate programmatically using -(id)initWithFrame: in my view controller
MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
[self.view insertSubview:myCustomView atIndex:0];
This works fine (although never calling [super init] and simply setting the object using the contents of the loaded nib seems a bit suspect – there is advice here to add a subview in this case which also works fine). However, I'd like to be able to instantiate the view from the storyboard also. So I can:
Place a UIView on a parent view in the storyboard
Set its custom class to MyCustomView
Override -(id)initWithCoder: – the code I've seen the most often fits a pattern such as the following:
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeSubviews];
}
return self;
}
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initializeSubviews];
}
return self;
}
-(void)initializeSubviews {
typeof(view) view = [[[NSBundle mainBundle]
loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] objectAtIndex:0];
[self addSubview:view];
}
Of course, this doesn't work, as whether I use the approach above, or whether I instantiate programatically, both end up recursively calling -(id)initWithCoder: upon entering -(void)initializeSubviews and loading the nib from file.
Several other SO questions deal with this such as here, here, here and here. However, none of the answers given satisfactorily fixes the problem:
A common suggestion seems to be to embed the entire class in a UIViewController, and do the nib loading there, but this seems suboptimal to me as it requires adding another file just as a wrapper
Could anyone give advice on how to resolve this problem, and get working outlets in a custom UIView with minimum fuss/no thin controller wrapper? Or is there an alternative, cleaner way of doing things with minimum boilerplate code?
Note that this QA (like many) is really just of historic interest.
Nowadays For years and years now in iOS everything's just a container view. Full tutorial here
(Indeed Apple finally added Storyboard References, some time ago now, making it far easier.)
Here's a typical storyboard with container views everywhere. Everything's a container view. It's just how you make apps.
(As a curiosity, KenC's answer shows exactly how, it used to be done to load an xib to a kind of wrapper view, since you can't really "assign to self".)
I'm adding this as a separate post to update the situation with the release of Swift. The approach described by LeoNatan works perfectly in Objective-C. However, the stricter compile time checks prevent self being assigned to when loading from the xib file in Swift.
As a result, there is no option but to add the view loaded from the xib file as a subview of the custom UIView subclass, rather than replacing self entirely. This is analogous to the second approach outlined in the original question. A rough outline of a class in Swift using this approach is as follows:
#IBDesignable // <- to optionally enable live rendering in IB
class ExampleView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
// below doesn't work as returned class name is normally in project module scope
/*let viewName = NSStringFromClass(self.classForCoder)*/
let viewName = "ExampleView"
let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
}
The downside of this approach is the introduction of an additional redundant layer in the view hierarchy which does not exist when using the approach outlined by LeoNatan in Objective-C. However, this could be taken as a necessary evil and a product of the fundamental way things are designed in Xcode (it still seems crazy to me that it is so difficult to link a custom UIView class with a UI layout in a way that works consistently over both storyboards and from code) – replacing self wholesale in the initializer before never seemed like a particularly interpretable way of doing things, although having essentially two view classes per view doesn't seem so great either.
Nonetheless, one happy result of this approach is that we no longer need to set the view's custom class to our class file in interface builder to ensure correct behaviour when assigning to self, and so the recursive call to init(coder aDecoder: NSCoder) when issuing loadNibNamed() is broken (by not setting the custom class in the xib file, the init(coder aDecoder: NSCoder) of plain vanilla UIView rather than our custom version will be called instead).
Even though we cannot make class customizations to the view stored in the xib directly, we are still able to link the view to our 'parent' UIView subclass using outlets/actions etc. after setting the file owner of the view to our custom class:
A video demonstrating the implementation of such a view class step by step using this approach can be found in the following video.
STEP1. Replacing self from Storyboard
Replacing self in initWithCoder: method will fail with following error.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Instead, you can replace decoded object with awakeAfterUsingCoder: (not awakeFromNib). like:
#implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
#end
STEP2. Preventing recursive call
Of course, this also causes recursive call problem. (storyboard decoding -> awakeAfterUsingCoder: -> loadNibNamed: -> awakeAfterUsingCoder: -> loadNibNamed: -> ...)
So you have to check current awakeAfterUsingCoder: is called in Storyboard decoding process or XIB decoding process.
You have several ways to do that:
a) Use private #property which is set in NIB only.
#interface MyCustomView : UIView
#property (assign, nonatomic) BOOL xib
#end
and set "User Defined Runtime Attributes" only in 'MyCustomView.xib'.
Pros:
None
Cons:
Simply does not work: setXib: will be called AFTER awakeAfterUsingCoder:
b) Check if self has any subviews
Normally, you have subviews in the xib, but not in the storyboard.
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
if(self.subviews.count > 0) {
// loading xib
return self;
}
else {
// loading storyboard
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
}
Pros:
No trick in Interface Builder.
Cons:
You cannot have subviews in your Storyboard.
c) Set a static flag during loadNibNamed: call
static BOOL _loadingXib = NO;
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
if(_loadingXib) {
// xib
return self;
}
else {
// storyboard
_loadingXib = YES;
typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
_loadingXib = NO;
return view;
}
}
Pros:
Simple
No trick in Interface Builder.
Cons:
Not safe: static shared flag is dangerous
d) Use private subclass in XIB
For example, declare _NIB_MyCustomView as a subclass of MyCustomView.
And, use _NIB_MyCustomView instead of MyCustomView in your XIB only.
MyCustomView.h:
#interface MyCustomView : UIView
#end
MyCustomView.m:
#import "MyCustomView.h"
#implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In Storyboard decoding path.
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
#end
#interface _NIB_MyCustomView : MyCustomView
#end
#implementation _NIB_MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In XIB decoding path.
// Block recursive call.
return self;
}
#end
Pros:
No explicit if in MyCustomView
Cons:
Prefixing _NIB_ trick in xib Interface Builder
relatively more codes
e) Use subclass as placeholder in Storyboard
Similar to d) but use subclass in Storyboard, original class in XIB.
Here, we declare MyCustomViewProto as a subclass of MyCustomView.
#interface MyCustomViewProto : MyCustomView
#end
#implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In storyboard decoding
// Returns MyCustomView loaded from NIB.
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self superclass])
owner:nil
options:nil] objectAtIndex:0];
}
#end
Pros:
Very safe
Clean; No extra code in MyCustomView.
No explicit if check same as d)
Cons:
Need to use subclass in storyboard.
I think e) is the safest and cleanest strategy. So we adopt that here.
STEP3. Copy properties
After loadNibNamed: in 'awakeAfterUsingCoder:', You have to copy several properties from self which is decoded instance f the Storyboard. frame and autolayout/autoresize properties are especially important.
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
// copy layout properities.
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
// copy autolayout constraints
NSMutableArray *constraints = [NSMutableArray array];
for(NSLayoutConstraint *constraint in self.constraints) {
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
if(firstItem == self) firstItem = view;
if(secondItem == self) secondItem = view;
[constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
// move subviews
for(UIView *subview in self.subviews) {
[view addSubview:subview];
}
[view addConstraints:constraints];
// Copy more properties you like to expose in Storyboard.
return view;
}
FINAL SOLUTION
As you can see, this is a bit of boilerplate code. We can implement them as 'category'.
Here, I extend commonly used UIView+loadFromNib code.
#import <UIKit/UIKit.h>
#interface UIView (loadFromNib)
#end
#implementation UIView (loadFromNib)
+ (id)loadFromNib {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self)
owner:nil
options:nil] objectAtIndex:0];
}
- (void)copyPropertiesFromPrototype:(UIView *)proto {
self.frame = proto.frame;
self.autoresizingMask = proto.autoresizingMask;
self.translatesAutoresizingMaskIntoConstraints = proto.translatesAutoresizingMaskIntoConstraints;
NSMutableArray *constraints = [NSMutableArray array];
for(NSLayoutConstraint *constraint in proto.constraints) {
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
if(firstItem == proto) firstItem = self;
if(secondItem == proto) secondItem = self;
[constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
for(UIView *subview in proto.subviews) {
[self addSubview:subview];
}
[self addConstraints:constraints];
}
Using this, you can declare MyCustomViewProto like:
#interface MyCustomViewProto : MyCustomView
#end
#implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
// copy additional properties as you like.
return view;
}
#end
XIB:
Storyboard:
Result:
Your problem is calling loadNibNamed: from (a descendant of) initWithCoder:. loadNibNamed: internally calls initWithCoder:. If you want to override the storyboard coder, and always load your xib implementation, I suggest the following technique. Add a property to your view class, and in the xib file, set it to a predetermined value (in User Defined Runtime Attributes). Now, after calling [super initWithCoder:aDecoder]; check the value of the property. If it is the predetermined value, do not call [self initializeSubviews];.
So, something like this:
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self && self._xibProperty != 666)
{
//We are in the storyboard code path. Initialize from the xib.
self = [self initializeSubviews];
//Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
//self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:#"backgroundColor"];
}
return self;
}
-(instancetype)initializeSubviews {
id view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
return view;
}
Don't forget
Two important points:
Set the File's Owner of the .xib to class name of your custom view.
Don't set the custom class name in IB for the .xib's root view.
I came to this Q&A page several times while learning to make reusable views. Forgetting the above points made me waste a lot of time trying to find out what was causing infinite recursion to happen. These points are mentioned in other answers here and elsewhere, but I just want to reemphasize them here.
My full Swift answer with steps is here.
There is a solution which is much more cleaner than the solutions above:
https://www.youtube.com/watch?v=xP7YvdlnHfA
No Runtime properties, no recursive call problem at all.
I tried it and it worked like a charm using from storyboard and from XIB with IBOutlet properties (iOS8.1, XCode6).
Good luck for coding!
I write a custom view CustomViewA : UIView<UITextFieldDelegate>and implements the delegate's methods.
And in the CustomViewA's init I write:
- (id)initWithFrame:(CGRect)frame {
self = [[[NSBundle mainBundle] loadNibNamed:#"CustomViewA" owner:self options:nil] objectAtIndex:0];
if (self) {
//the txtfieldA is a IBOutlet property linked to the nib file.
self.txtfieldA.delegate = self; //not work
}
return self;
}
And in this xib file includes a UITextField control which I set its delegate in the init method.But when I run this and edit the textfield, the delegate's methods are not called.Can anyone tell me why? And how can I fix it??
To load a view which subclass an UIView from a xib file, you can do this way:
// Reusable method to load a class which subclass UIView from a xib.
+ (id)loadNibNamed:(NSString *)nibName ofClass:(Class)objClass andOwner:(id)owner {
if (nibName && objClass) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:nibName owner:owner options:nil];
for (id currentObject in objects ){
if ([currentObject isKindOfClass:objClass])
return currentObject;
}
}
return nil;
}
Then add the view in your controller:
myCustomViewA = [self loadNibNamed:#"customviewA" ofClass:[CustomViewA class] andOwner:self];
myCustomViewA.delegate = self; // Or do this in the xib file
[self.view addSubview:myCustomViewA];
Ensure that that the UITextField is correctly wired to the IBOutlet of of CustomViewA.
Otherwise, attempting to set the delegate in init will do nothing,
//if self.myTextField is nil, then this does nothing
self.myTextField.delegate = self;
just simply do not use -(id)init if you init a view from xib files.
and set your UITextField's delegate method in - (void)awakeFromNib..
I'm developing an iOS app with latest SDK and XCode 4.2.
I want to use a custom XIB in a custom UIView.
My custom UIView:
#interface CoordinateView : UIView {
/**
*/
ARGeoCoordinate *geoCoordinate;
/**
*/
IBOutlet UILabel* title;
/**
*/
IBOutlet UILabel* subTitle;
/**
*/
IBOutlet UIImageView* image;
}
Implementation:
#implementation CoordinateView
#synthesize geoCoordinate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
//
NSArray* topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CoordinateView" owner:nil options:nil];
for(id currentObject in topLevelObjects)
{
if ([currentObject isKindOfClass:[CoordinateView class]])
{
[self addSubview:currentObject];
break;
}
}
}
return self;
}
- (id)initForCoordinate:(ARGeoCoordinate *)coordinate
{
self.geoCoordinate = coordinate;
CGRect theFrame = CGRectMake(0, 0, BOX_WIDTH, BOX_HEIGHT);
self = [self initWithFrame:theFrame];
if (self)
{
title.text = geoCoordinate.title;
image.image = [UIImage imageNamed:[NSString stringWithFormat:#"03%d.jpg", geoCoordinate.id]];
}
return self;
}
To initialize CoordinateView I use initWithCoordinate method.
Debugging I've found that title is nil here inside initWithCoordinate:
title.text = geoCoordinate.title;
I've used Interface Builder to 'link' IBOutlets.
What I'm doing wrong?
I ran into the same issue a while back. Here is what I did to fix it:
In the xib, open the document outline (the left pane that shows the list of views and their subviews etc).
Control left Click on the view representing your class (in your case CoordinateView) and drag the blue line down to the subviews (in your case the UILabel called title)
XCode should then bring up a small dialog box with the option "title" (because your label is called title.)
Click on title and wallaa.
Go back to your debugging and check if your outlets are still nil.
Just a personal preference (not necessarily related to the topic on discussion): I would call your UILabel 'titleLabel' instead of 'title' because [objectInstance title] looks like it would return an NSString to someone who does not know that 'title' is a UILabel. Just a personal preference, thats all.
Cheers
In your initWithFrame: method, you should assign self to currentObject instead of adding it as a subview.
Also there's no need to override initWithFrame: and calling the super method since the layout of your view is determined by your xib file.
When the view is totally loaded, the viewDidLoad method is called. So, you should initialize the elements within the view there:
- (void)viewDidLoad {
title.text = self.geoCoordinate.title;
}
More info in the docs.
Ok, here's another question.
I am creating a UIView called ProgressView that is a semi-transparent view with an activity indicator and a progress bar.
I want to be able to use this view throughout different view controllers in my app, when required.
I know of 3 different ways of doing this (but I am only interested in one):
1) Create the entire view programatically, instantiate and configure as required. No worries I get that one.
2) Create the UIView in interface builder, add the required objects and load it using a method like the below. Problem with this is that we are basically guessing that the view is the objectAtIndex:0 because nowhere in the documentation I found a reference to the order of the elements returned from the [[NSBundle mainBundle] loadNibName: function.
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"yournib"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
myView.frame = CGRectMake(0,0,300,400); //or whatever coordinates you need
[scrollview addSubview:myView];
3) Subclass UIViewController and let it manage the view as per normal. In this case I would never be actually pushing the view controller onto the stack, but only its main view:
ProgressViewController *vc = [[ProgressViewController alloc] initWithNibName:#"ProgressView" bundle:nil];
[vc.view setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
[self.view addSubview:vc.view];
[vc release];
As far as I can tell, #3 is the the correct way of doing this (apart from programatically) but I am not entirely sure if it is safe to release the ProgressView's view controller whilst another controller's view is retaining its main view (gut feel says it is going to leak?)?
What do I do in terms of memory management in this case, where and when should I release the ProgressView's view controller?
Thanks in advance for your thoughts.
Cheers,
Rog
I think that your solution #3 adds unnecessary complexity by introducing a UIViewController instance just as a container for your ProgressView so that you can setup nib bindings. While I do think that it is nice to be able to work with an IBOutlet bound property rather than iterating through the nib contents you can do so without introducing a UIViewController whose behavior you neither need nor want. This should avoid your confusion around how and when to release the view controller and what, if any, side effects it might have on the responder chain or other behaviors of the loaded view.
Instead please reconsider using NSBundle and taking advantage of the power of that owner argument.
#interface ProgressViewContainer : NSObject {
}
#property (nonatomic, retain) IBOutlet ProgressView *progressView;
#end
#implementation ProgressViewContainer
#synthesize progressView = progressView;
- (void) dealloc {
[progressView release];
[super dealloc];
}
#end
#interface ProgressView : UIView {
}
+ (ProgressView *) newProgressView;
#end
#implementation ProgressView
+ (ProgressView *) newProgressView {
ProgressViewContainer *container = [[ProgressViewContainer alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:container options:nil];
ProgressView *progressView = [container.progressView retain];
[container release];
return progressView;
}
#end
Create a nib named "ProgressView" containing a ProgressView and set it's File's Owner class to ProgressViewContainer. Now you can create ProgressViews loaded from your nib.
ProgressView *progressView = [ProgressView newProgressView];
[scrollView addSubview:progressView];
[progressView release];
If you have multiple configurations of your progress view then maybe you'll want to implement a -initWithNibNamed: method on ProgressView instead of +newProgressView so you can specify which nib to use to create each ProgressView instance.
I vote for option #2. The return value from -[NSBundle loadNibNamed] is an array of the top-level objects. So as long as you have just one top level object in your nib, then the index 0 will be correct. The other views are subviews and not top level objects.
Another option of course is to do something like create a superclass for all of your view controllers that includes an outlet called something like 'progressView' and then connect your view to that outlet on file's owner in the nib. Seems like overkill for this, though.
I also prefer alternative #2. If the "0" is bothering you, you could:
Create a subclass of UIView called ProgressView
Create a nib-file called ProgressView.xib describing your progress view.
Select the topmost view in your nib, and set its Class to ProgressView in interface builder
then do
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:self options:nil];
ProgressView *progressView = nil;
for (UIView *view in nibContents) {
if ([view isKindOfClass:[ProgressView class]]) {
progressView = (ProgressView *) view;
break;
}
}
if (progressView != nil) {
//Use progressView here
}
I ended up adding a category to UIView for this:
#import "UIViewNibLoading.h"
#implementation UIView (UIViewNibLoading)
+ (id) loadNibNamed:(NSString *) nibName {
return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
}
+ (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
if(!nib) return nil;
UIView * target = nil;
for(UIView * view in nib) {
if(view.tag == tag) {
target = [view retain];
break;
}
}
if(target && [target respondsToSelector:#selector(viewDidLoad)]) {
[target performSelector:#selector(viewDidLoad)];
}
return [target autorelease];
}
#end
explanation here: http://gngrwzrd.com/blog-view-controller-less-view-loading-ios-mac.html