82

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:

  1. 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;
    }
    
  2. 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:

  1. Place a UIView on a parent view in the storyboard
  2. Set its custom class to MyCustomView
  3. 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?

Community
  • 1
  • 1
Ken Chatfield
  • 3,277
  • 3
  • 22
  • 27
  • 1
    Did you ever get a satisfactory answer for this? I'm struggling for this at the moment. All the other answers don't seem quite good enough, as you mention. You could always answer the question yourself if you've found out anything in the past few months. – Mike Meyers Jul 03 '14 at 03:20
  • 13
    Why is it so difficult to create reusable views in iOS? – clocksmith Aug 23 '14 at 15:50
  • Ken - simply don't do this, today! Use a container view. http://stackoverflow.com/a/23403979/294884 The age-old "how the hell do you load xibs" business is history. Eg, http://stackoverflow.com/a/21073901/294884, http://stackoverflow.com/a/15406012/294884 – Fattie Sep 15 '14 at 13:00
  • You are on the right track with your solution. The important thing is that the view you load from the xib file should be a normal UIView and for dragging outlets change the File's Owner object into your custom class and use that. – Bogdan Onu Oct 03 '14 at 15:33
  • Hi @MikeMeyers – unfortunately I didn't, and ended up just resorting to loading all views programatically in the end (as described in the first part of the question) but LeoNatan's strategy seems to be the best solution for now without resorting to embedding in an extra container UIView using `view:addSubview:` in Objc-C. The story is different in Swift, as it's not possible to assign to self due to the (probably rightly) fussier compile time checks, so will post about that separately. More generally, JoeBlow's advice about container views is also worth reading. – Ken Chatfield Dec 25 '14 at 09:50
  • [Swift example and answer here.](http://stackoverflow.com/a/34524346/3681880) – Suragch Dec 30 '15 at 07:23
  • 1
    Thanks @Suragch! [There's also an answer which details how to do this in Swift further down the page](http://stackoverflow.com/questions/21898190/creating-a-reusable-uiview-with-xib-and-loading-from-storyboard/27647411#27647411) – Ken Chatfield Dec 30 '15 at 13:13
  • 1
    In fact, the answer you link to uses exactly the same approach (though your answer does not include an init from rect function, meaning that it can only be initialized from the storyboard and not programatically) – Ken Chatfield Dec 30 '15 at 13:13
  • 1
    regarding this very old QA, Apple finally introduced STORYBOARD REFERENCES ... https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/Chapters/AddSBReference.html ... so that's that, phew! – Fattie May 30 '16 at 13:07
  • Good to know, though that still doesn't help if you want to create an independent view in a separate xib file, without a view controller, as described in the question unfortunately! – Ken Chatfield May 30 '16 at 19:16

6 Answers6

26

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.

enter image description here

(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".)

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • The problem with this is that you will end up with a lot of ViewControllers for all you enbedded content views. – Bogdan Onu Oct 03 '14 at 15:36
  • Hi @BogdanOnu ! You should have many, many, many view controllers. For the "smallest" thing - you should have a view controller. – Fattie Oct 04 '14 at 19:56
  • 2
    This is very useful – thanks @JoeBlow. Using container views definitely seems to be an alternative approach, and a simple way to avoid all the complications of dealing with xibs directly. However, it doesn't seem 100% satisfactory alternative for creating reusable components for distribution/use across projects, as it requires all the UI design to be embedded in the storyboard directly. – Ken Chatfield Dec 25 '14 at 01:39
  • 3
    I have less of a problem with the use of an extra ViewController in this case as it would just contain the program logic that would otherwise belong in the custom View class in the xib case, but the tight coupling with the storyboard means that I'm not sure container views can solve this problem entirely. Perhaps in most practical situations views are project-specific, and so this is the best and most 'standard' solution, but am surprised there is still no easy way of packaging custom views for storyboard use. My programmer's impulse to divide and conquer into isolated components is itching ;) – Ken Chatfield Dec 25 '14 at 01:40
  • 1
    Also, with the introduction of live rendering of custom UIView subclasses in Xcode 6, I'm not sure whether I buy the premise that creating views using xibs in this way is now deprecated – Ken Chatfield Dec 25 '14 at 01:53
  • Hi Ken -- you're wuite right. The simple fact is, **Apple gives no good way to 'package' as you outline**. It's just one of those weird things. It's like if you're a game programmer, in Unity, there quite simply is no decent way to package 'modules' - it's just sort of one of those nutty things about the universe. ! Merry Xmas! – Fattie Dec 25 '14 at 14:29
  • And what about the apps which are usings xibs instead of storyboard? Or use storyboard as a sceleton but use xibs for implement the details of a screen? I can use a subview in xib unlike container view, this can use only programaticaly in this case. – Balazs Nemeth Jan 09 '15 at 23:04
  • Unfortunately, this problem is NOT "history" in iOS. Container Views are useful but they also add a lot of screen clutter, and their own problems when it comes to automatically resizing to the content size. It wouldn't be so bad, except that Interface Builder's Storyboard editor is so appallingly bad. It makes it very hard to do serious work with many Container Views. Reusable XIB-based views are still a very useful concept. – Womble Aug 06 '15 at 04:49
  • Will this still be a solution, when you have multiple similar subviews in superview that you may want to share a single xib file as view template, and modify content on it. Can you replace the xib with ViewContainer in this case. – Scott Zhu Nov 10 '15 at 04:43
  • Will this container view approach work if I want to create a reusable view that I can also use in a UITableViewCell? – sleep Mar 31 '16 at 01:51
25

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:

Setting the file owner property of the custom view

A video demonstrating the implementation of such a view class step by step using this approach can be found in the following video.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Ken Chatfield
  • 3,277
  • 3
  • 22
  • 27
  • Hi Joe – thanks for your comments, that's a lot of bold (as in many of your other comments!) As I stated in reply to your answer, I agree that for most situations container views are probably the best approach, but in those cases where views must be used across projects (or distributed) it does make sense at least to me and seemingly to others also to have an alternative. You may personally believe it is bad style, but perhaps you could just let the many other posts here suggesting how this might be done for reference, and let people judge for themselves. Both approaches seem to be useful. – Ken Chatfield Oct 14 '15 at 22:26
  • Thanks for this. I tried a number of different approaches in Swift without success until I heeded your advice about leaving the nib's class as `UIView`. I agree it is insane that Apple have never made this easy, and now it's virtually impossible. A container is not always the answer. – Echelon Oct 22 '15 at 09:45
17

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:

XIB screenshot

Storyboard:

Storyboard

Result:

enter image description here

rintaro
  • 51,423
  • 14
  • 131
  • 139
  • 3
    The solution is more complicated than the initial problem. To stop the recursive loop you just need to set the File's Owner object instead of declaring the content view as the type MyCustomView class type. – Bogdan Onu Oct 03 '14 at 15:39
  • It's just a trade-off of a)simple initializing process but complicated view hierarchy and b)complicated initializing process but simple view hierarchy. n'est-ce pas? ;) – rintaro Oct 03 '14 at 16:27
  • is there any download link for this project? – karthikeyan Jul 17 '15 at 16:59
14

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;
}
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Thanks @LeoNatan! I'm accepting this answer as it is the best solution to the problem as originally stated. However, note that it is no longer possible in Swift – I've added some separate notes on a possible work around in that case. – Ken Chatfield Dec 25 '14 at 13:02
  • @KenChatfield I noticed that in my Swift sub project, and got annoyed by it. I am not sure what they are thinking, because without this, a lot of the Cocoa/Cocoa Touch internal implementation is impossible in Swift. My bet is there will be some dynamic features when they actually have time to focus on features rather than bugs. Swift is not ready at all, and worst offenders are the development tools. – Léo Natan Dec 25 '14 at 13:28
  • Yes you're right, Swift has a lot of rough edges right now. Having said that, it does seem that assigning directly to self was a bit of a hack, so I'm happy in a way that the compiler now warns against such things (this tighter type checking seems to be one of the nice things about the language). However, you're right that it makes the linking of custom views with xibs very messy and a little unsatisfactory. Let's hope as you say that once they've finished sorting out the bugs, we'll see some more dynamic features to help us out a little more with stuff like this! – Ken Chatfield Dec 25 '14 at 14:20
  • 1
    It's not a hack, it's how class clusters work. And really, there is no technical issue to allow the return of an object that is a subclass of the return class to be returned instead. As it is, one of the cornerstones of Cocoa and Cocoa Touch - class clusters - is impossible to implement. Terrible. Some frameworks, like Core Data, cannot be implementable in Swift, which makes it a useless language for most of my uses. – Léo Natan Dec 26 '14 at 00:56
  • Interesting! I wasn't familiar with the class cluster pattern, although it looks like a [very neat way of abstracting the factory pattern in Obj-C without a separate factory object](http://stackoverflow.com/a/2459385/1085548). I guess that Swift is a new language that just so happens to support interfacing with the Cocoa libraries, so perhaps the fact that the Cocoa libraries are not directly implementable using the same Obj-C paradigms is not surprising. What is is the lack of an alternative set of 'Swift' design patterns, but hopefully they will come. Thanks for the very insightful comments! – Ken Chatfield Dec 26 '14 at 13:56
  • I also agree that the limitations of the language (particularly when combined with the still Obj-c centric interface builder) are still very much apparent in situations such as this though. Not being able to implement something as simple as a custom UIView in a single class is just crazy. – Ken Chatfield Dec 26 '14 at 13:59
  • 1
    Somehow it did not worked for me (iOS8.1 SDK). I set a restorationIdentifier in the XIB instead of a runtime attribute, than it worked. E.g I set the "MyViewRestorationID" in the xib, than in the initWithCoder: I checked that the ![[self restorationIdentifier] isEqualToString:@"MyViewRestorationID"] – Balazs Nemeth Jan 09 '15 at 11:55
  • 1
    This does not work, at least on iOS 10, as the binding of the "User Defined Runtime Attribute" does not happen until -initWIthCoder: is already done. – chadbag Sep 14 '17 at 19:40
  • @chadbag Sad but true. I succeeded by using the restoration identifier, but the OS now does a runtime check and prevents self modification in `initWithCoder:`, so AFAICT having a generic view designed in a xib in now fully impossible. – Frizlab Nov 23 '21 at 11:35
  • Oh wait! Apparently `awakeAfterUsingCoder:` has been created _specifically_ to do just that! I have to try… – Frizlab Nov 23 '21 at 12:39
  • Long story short, it does not work either. – Frizlab Nov 25 '21 at 14:57
14

Don't forget

Two important points:

  1. Set the File's Owner of the .xib to class name of your custom view.
  2. 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.

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • This was the answer for me, I had set BOTH - you only need file owner, if you set the view's main content to the custom class it creates an infinite loop – devjme Nov 29 '21 at 20:55
2

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!

Balazs Nemeth
  • 2,333
  • 19
  • 29
  • 1
    Thanks @ingaham! However, the approach outlined in the video is identical to the second solution proposed in the original question (Swift code for which is presented in my answer above). As in both of those cases, it involves adding a subview to a wrapper UIView subclass, and this is why there is no issue with recursive calls, nor need to rely on runtime properties or anything else complicated. As mentioned, the downside is that a redundant additional child view needs to be added to the custom UIView class. As discussed though, this may as you say be the best and simplest solution for now. – Ken Chatfield Jan 10 '15 at 20:09
  • Yes, you're totally right, these are identical solutions. However, a redundant view is necessary, but it is the most clean and well-maintainable solution. So I decided to use this one. – Balazs Nemeth Jan 11 '15 at 21:08
  • 1
    I believe a redundant view is totally and completely natural, and there can never be another solution. Note that you're saying **"'something' is going to go 'here'"** ...that "here" is a natural existing thing. There simply has to be "something" there, a "place you're going to put things" - the very definition of what a "view" is. And wait! Apple's "container view" stuff **is, indeed, precisely that** .. there's a "frame", a "holder view" (the "container view") which you segue something in to. Indeed, the 'redundant' view solutions are precisely, a hand-made container view! Just use Apple's. – Fattie Oct 14 '15 at 13:02