Search code examples
uiviewswiftsubclassing

Issue with initWithFrame when subclassing UIView in Swift


I have an Objective-C UIView class:

ChoosePersonView.h

@class Person;

@interface ChoosePersonView : MDCSwipeToChooseView

@property (nonatomic, strong, readonly) Person *person;

- (instancetype)initWithFrame:(CGRect)frame
                       person:(Person *)person
                      options:(MDCSwipeToChooseViewOptions *)options;

@end

MDCSwipeToChooseView.m

@class MDCSwipeToChooseViewOptions;

@interface MDCSwipeToChooseView : UIView

@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIView *likedView;
@property (nonatomic, strong) UIView *nopeView;

- (instancetype)initWithFrame:(CGRect)frame
                      options:(MDCSwipeToChooseViewOptions *)options;

@end

Normally I would subclass and use it like the following:

ChoosePersonView.m

@implementation ChoosePersonView

#pragma mark - Object Lifecycle

- (instancetype)initWithFrame:(CGRect)frame
                       person:(Person *)person
                      options:(MDCSwipeToChooseViewOptions *)options {
    self = [super initWithFrame:frame options:options];
    if (self) {
        _person = person;
        self.imageView.image = _person.image;

        self.autoresizingMask = UIViewAutoresizingFlexibleHeight |
                                UIViewAutoresizingFlexibleWidth |
                                UIViewAutoresizingFlexibleBottomMargin;
        self.imageView.autoresizingMask = self.autoresizingMask;

        [self constructInformationView];
    }
    return self;
}

...
@end

The problem I'm facing is when I try to do this in Swift, the property self.imageView is always nil.

I'm not sure how to re-implement initWithFrame as it's implemented in Objective-C within Swift. I thought using init(frame: CGRect, person: Person, options: MDCSwipeToChooseViewOptions) was the correct way but I think I may be wrong.

ChoosePersonView.swift

class ChoosePersonView: MDCSwipeToChooseView {
    var informationView: UIView!
    var nameLabel: UILabel!
    var cameraImageLabelView: ImageLabelView!
    var interestsImageLabelView: ImageLabelView!
    var friendsImageLabelView: ImageLabelView!
    var person: Person!
    let ChoosePersonViewImageLabelWidth = CGFloat(42.0)

    // #pragma mark - Object Lifecycle

    init(frame: CGRect, person: Person, options: MDCSwipeToChooseViewOptions) {
        super.init(frame: frame)

        self.person = person
        self.imageView.image = self.person.image // self.imageView is nil.
        self.autoresizingMask = .FlexibleHeight | .FlexibleWidth | .FlexibleBottomMargin
        self.imageView.autoresizingMask = self.autoresizingMask

        self.constructInformationView()
    }
    ....
}

Solution

  • self.imageView is nil because you are not doing anything to make it not be nil. You do not show any code that would make it not be nil, so it is hard to guess how you imagine that is supposed to happen.

    My guess is that it is the job of MDCSwipeToChooseView's initializer, which you would call as super.init(frame:options:), to create imageView. It's hard to say, because you don't show the code. Certainly, that is the initializer you were calling when ChoosePersonView was written in Objective-C. So why are you not calling it now too, now that it is written in Swift? For some reason, you have elected not to call that initializer - you are calling super.init(frame:) instead. That seems a very strange thing to do. Perhaps that's where you're going wrong.

    But in any case I can guarantee you that if you don't call some code that causes imageView not to be nil, it will be nil, because that is the default value for all object properties in Objective-C.