How to Create a Custom iOS View Class and Instantiate Multiple Copies of It (In Ib)

How do I create a custom iOS view class and instantiate multiple copies of it (in IB)?

Well to answer conceptually, your timer should likely be a subclass of UIView instead of NSObject.

To instantiate an instance of your timer in IB simply drag out a UIView drop it on your view controller's view, and set it's class to your timer's class name.

Sample Image

Remember to #import your timer class in your view controller.

Edit: for IB design (for code instantiation see revision history)

I'm not very familiar at all with storyboard, but I do know that you can construct your interface in IB using a .xib file which is nearly identical to using the storyboard version; You should even be able to copy & paste your views as a whole from your existing interface to the .xib file.

To test this out I created a new empty .xib named "MyCustomTimerView.xib". Then I added a view, and to that added a label and two buttons. Like So:

Sample Image

I created a new objective-C class subclassing UIView named "MyCustomTimer". In my .xib I set my File's Owner class to be MyCustomTimer. Now I'm free to connect actions and outlets just like any other view/controller. The resulting .h file looks like this:

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

The only hurdle left to jump is getting this .xib on my UIView subclass. Using a .xib dramatically cuts down the setup required. And since you're using storyboards to load the timers we know -(id)initWithCoder: is the only initializer that will be called. So here is what the implementation file looks like:

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])){
[self addSubview:
[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView"
owner:self
options:nil] objectAtIndex:0]];
}
return self;
}
- (IBAction)startButtonPush:(id)sender {
self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

The method named loadNibNamed:owner:options: does exactly what it sounds like it does. It loads the Nib and sets the "File's Owner" property to self. We extract the first object in the array and that is the root view of the Nib. We add the view as a subview and Voila it's on screen.

Obviously this just changes the label's background color when the buttons are pushed, but this example should get you well on your way.

Notes based on comments:

It is worth noting that if you are getting infinite recursion problems you probably missed the subtle trick of this solution. It's not doing what you think it's doing. The view that is put in the storyboard is not seen, but instead loads another view as a subview. That view it loads is the view which is defined in the nib. The "file's owner" in the nib is that unseen view. The cool part is that this unseen view is still an Objective-C class which may be used as a view controller of sorts for the view which it brings in from the nib. For example the IBAction methods in the MyCustomTimer class are something you would expect more in a view controller than in a view.

As a side note, some may argue that this breaks MVC and I agree somewhat. From my point of view it's more closely related to a custom UITableViewCell, which also sometimes has to be part controller.

It is also worth noting that this answer was to provide a very specific solution; create one nib that can be instantiated multiple times on the same view as laid out on a storyboard. For example, you could easily imagine six of these timers all on an iPad screen at one time. If you only need to specify a view for a view controller that is to be used multiple times across your application then the solution provided by jyavenard to this question is almost certainly a better solution for you.

Define custom NSAssert that includes line, class and general formatting

The NSAssert macro is defined like this:

#define NSAssert(condition, desc, ...)  /* the implementation */

So, the condition is already a separate parameter from the format string and the variable argument list. There should be no problem doing something similar to what you did for NSLog:

#define MyAssert(condition, desc, ...) \
NSAssert(condition, (@"%@: " desc), NSStringFromClass([self class]), ##__VA_ARGS__)

Infinite loop on instantiated view from xib on storyboard

On Interface Builder, where defined your view, you should set the File Owner's custom class to ItemView (or to other classes you are creating). Do not set the view class.

Unfortunately, StackOverflow won't let me post images, but see the screenshots below.

View with no class

File owner's class

Class not overriding storyboard

The right way to create a custom UIView is to have a shared function that you call from both init(frame:) and init?(coder:) which will make a guarantee that it'll behave the same whether it's setted as a class in IB or instantiated in code

class CustomView: UIView {

override init(frame: CGRect) {
super.init(frame: frame)
sharedLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedLayout()
}
func sharedLayout() {
// all the layout code from above
}

}

Instantiate view with XIB

You will have to add -loadNibNamed method like following:

Add the following code to your Your_View init method:

NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"Your_nib_name" owner:self options:nil];
UIView *mainView = [subviewArray objectAtIndex:0];
[self addSubview:mainView];

Refer these two questions here:

Adding a custom subview (created in a xib) to a view controller's view - What am I doing wrong

iOS: Custom view with xib

EDIT:

In your ViewController.m file

#import CustomView.h   <--- //import your_customView.h file

- (void)viewDidLoad
{
[super viewDidLoad];

CustomView *customView = [[CustomView alloc]init];
[self.view addSubview:customView];
}


Related Topics



Leave a reply



Submit