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

I am currently making an app that will have multiple timers, which are basically all the same.

I want to create a custom class that uses all of the code for the timers as well as the layout/animations, so I can have 5 identical timers that operate independently of each other.

  • Change app name in apple store of an already released/available iOS app
  • Interlocked operations in iOS?
  • XCode unit testing: Error while reading test scope bundle
  • IOS: add imageview in a scrollview to have zoom
  • Wait until multiple networking requests have all executed - including their completion blocks
  • How to invalidate an iOS push notification on iPad when handled on iPhone?
  • I created the layout using IB (xcode 4.2) and all the code for the timers is currently just in the viewcontroller class.

    I am having difficulty wrapping my brain around how to encapsulate everything into a custom class and then add it to the viewcontroller, any help would be much appreciated.

    4 Solutions Collect From Internet About “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.

    enter image description here

    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:

    enter image description here

    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.

    Swift example

    Updated for Swift 3

    Here is a basic walk through. I learned a lot of this from watching this Youtube video series.

    Add custom view files

    The following two files will form your custom view:

    • .xib file to contain the layout
    • .swift file as UIView subclass

    The details for adding them are below.

    Xib file

    Add a .xib file to your project (File > New > File… > User Interface > View). I am calling mine ReusableCustomView.xib.

    Create the layout that you want your custom view to have. As an example, I will make a layout with a UILabel and a UIButton. It is a good idea to use auto layout so that things will resize automatically no matter what size you set it to later.

    enter image description here

    Swift file

    Add a .swift file to your project (File > New > File… > Source > Swift File). It is a subclass of UIView and I am calling mine ReusableCustomView.swift.

    import UIKit
    class ResuableCustomView: UIView {
    
    }
    

    Make the Swift file the owner

    Go back to your .xib file and click on “File’s Owner” in the Document Outline. In the Identity Inspector write the name of your swift file as the custom class name.

    enter image description here

    Add Custom View Code

    Replace the ReusableCustomView.swift file’s contents with the following code:

    import UIKit
    
    class ResuableCustomView: UIView {
    
        @IBOutlet var view: UIView!
        @IBOutlet weak var label: UILabel!
    
        @IBAction func buttonTap(_ sender: UIButton) {
            label.text = "Hi"
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            Bundle.main.loadNibNamed("ReusableCustomView", owner: self, options: nil)
            self.addSubview(view)
            view.frame = self.bounds
        }
    }
    

    Be sure to get the spelling right for the name of your .xib file.

    Hook up the Outlets and Actions

    Hook up the outlets and actions by either (1) control dragging from the xib layout to the swift custom view code, or (2) right-clicking on the document outline and dragging the plus button up to the File’s Ownwer. Method 2 is illustrated below.

    enter image description here

    Use you custom view

    Your custom view is finished now. All you have to do is add a UIView wherever you want it in your main storyboard. Set the class name of the view to ReusableCustomView in the Identity Inspector.

    enter image description here

    At the time of this writing I was having trouble adding @IBDesignable to the code, so the views don’t show up in IB but they do when you run your app.

    enter image description here

    Answer for view controllers, not views:

    There is an easier way to load a xib from a storyboard.
    Say your controller is of MyClassController type which inherit from UIViewController.

    You add a UIViewController using IB in your storyboard; change the class type to be MyClassController. Delete the view that had been automatically added in the storyboard.

    Make sure the XIB you want called is called MyClassController.xib.

    When the class will be instantiated during the storyboard loading, the xib will be automatically loaded.
    The reason for this is due to default implementation of UIViewController which calls the XIB named with the class name.

    This is not really an answer, but I think it is helpful to share this approach.

    Objective-C

    1. Import CustomViewWithXib.h and CustomViewWithXib.m to your
      project
    2. Create the custom view files with the same name (.h / .m /
      .xib)
    3. Inherit your custom class from CustomViewWithXib

    Swift

    1. Import CustomViewWithXib.swift to your project
    2. Create the custom view files with the same name (.swift and .xib)
    3. Inherit your custom class from CustomViewWithXib

    Optional :

    1. Go to your xib file, set the owner with your custom class name if you
      need to connect some elements (for more details see the part Make
      the Swift file the owner of @Suragch answer’s)

    It’s all, now you can add your custom view into your storyboard and it will be shown 🙂

    CustomViewWithXib.h :

     #import <UIKit/UIKit.h>
    
    /**
     *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
     MyCustomView.h
     MyCustomView.m
     MyCustomView.xib
     */
    
    // This allows seeing how your custom views will appear without building and running your app after each change.
    IB_DESIGNABLE
    @interface CustomViewWithXib : UIView
    
    @end
    

    CustomViewWithXib.m :

    #import "CustomViewWithXib.h"
    
    @implementation CustomViewWithXib
    
    #pragma mark - init methods
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            // load view frame XIB
            [self commonSetup];
        }
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            // load view frame XIB
            [self commonSetup];
        }
        return self;
    }
    
    #pragma mark - setup view
    
    - (UIView *)loadViewFromNib {
        NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    
        //  An exception will be thrown if the xib file with this class name not found,
        UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
        return view;
    }
    
    - (void)commonSetup {
        UIView *nibView = [self loadViewFromNib];
        nibView.frame = self.bounds;
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        // Adding nibView on the top of our view
        [self addSubview:nibView];
    }
    
    @end
    

    CustomViewWithXib.swift :

    import UIKit
    
    @IBDesignable
    class CustomViewWithXib: UIView {
    
        // MARK: init methods
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            commonSetup()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            commonSetup()
        }
    
        // MARK: setup view 
    
        private func loadViewFromNib() -> UIView {
            let viewBundle = NSBundle(forClass: self.dynamicType)
            //  An exception will be thrown if the xib file with this class name not found,
            let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
            return view as! UIView
        }
    
        private func commonSetup() {
            let nibView = loadViewFromNib()
            nibView.frame = bounds
            // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
            nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
            // Adding nibView on the top of our view
            addSubview(nibView)
        }
    }
    

    You can find some examples here.

    Hope that helps.