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.
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:
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
How to Prevent Tableview Section Head from Sticking While Scrolling
Wkwebview Origin Null Is Not Allowed by Access-Control-Allow-Origin
Swift Parse Json - the Data Couldn'T Be Read Because It Isn'T in the Correct Format
How to Find Current Visible Viewcontroller in Ios
Save Generated Gif to Camera Roll
How to Add Pagecontrol Inside Uicollectionview Image Scrolling
React Native App Crashes on Launch Screen on Device
Best Way to Check If Object Is Out of Bounds in Array
Swift Tableview in a Uiview Not Displaying Data
What Happens When Testflight App Expire
How to Change Button Title Alignment in Swift
How to Programmatically Get the MAC Address of an Iphone
How to Create a Basic Uibutton Programmatically
Cell Spacing in Uicollectionview
How to Apply Gradient to Background View of iOS Swift App
Conditional Binding: If Let Error - Initializer For Conditional Binding Must Have Optional Type