コードでもUIStoryboard上でも使える使い回し可能なカスタムUIViewをXibで作る

コードでもUIStoryboard上でも使える使い回し可能なカスタムUIViewをXibで作るXibで作成したカスタムビューをUIStoryboardに置いてそれが、ちゃんと表示されたらいいですよね。ちゃんと、Autolayoutにも対応したりして。で、更にその同じXibで作成したクラスをコードからも使えたらいいですよね、ってことで前から書きたいと思っていたのですが、やってみたので書いてみます。

そうそう、これに着いてリンクを教えて頂いた方ありがとうございました!

と言っても先駆者はいるので、先駆者のやり方を参考にした上で組み合わせて自分なりのカスタムビューを作ってみました。

先駆者の記事

Xibで作ったビューをUIStoryboard上に配置して使いまわす

まずは先駆者の記事の1番めを参考に、コードを書いていきます。その前に使い回しクラスとXibを作ります。

Class

こんな感じで、作りました。XibはUIViewのクラス名と同名にします。自分規約です。こうすることで後ほど説明するコードが汎用的に使えるようになります。

Xibをいじります。まず、File’s OwnerにReusableViewをセットします。

Owner

そして、UIViewのオブジェクトを一番下において、その上に好きなようにUIViewを配置していきます。

Reusableview

こんなのを作ってみました。一番下はUIView。その上にUIImageが3つ乗っています。左上のAutolayout的にはこうなっています。

Auto

他のものも線で簡単に説明するとこうなります。

Reusableview auto

コードを書いていきます。ReusableView.mを開いて下記のように書きます。

#import "ReusableView.h"

@implementation ReusableView

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self)
    {
        NSBundle *mainBundle = [NSBundle mainBundle];
        NSArray *loadedViews = [mainBundle loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
        UIView *loadedSubview = [loadedViews firstObject];
        
        [self addSubview:loadedSubview];
        
        loadedSubview.translatesAutoresizingMaskIntoConstraints = NO;
        
        [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeTop]];
        [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeLeft]];
        [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeBottom]];
        [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeRight]];
    }
    return self;
}

- (NSLayoutConstraint *)pin:(id)item attribute:(NSLayoutAttribute)attribute
{
    return [NSLayoutConstraint constraintWithItem:self
                                        attribute:attribute
                                        relatedBy:NSLayoutRelationEqual
                                           toItem:item
                                        attribute:attribute
                                       multiplier:1.0
                                         constant:0.0];
}

@end

はい。準備は出来ました。このReusableViewをUIStoryboard上で扱ってみたいと思います。まず、とりあえずProjectをSingleViewで作成したので、最初のUIViewControllerに適当なサイズのUIViewを置きます。わかりやすいように緑色にしました。Autolayoutはちょっと見にくいですが先ほどと色を揃えて黄色で書いています。

Gview

この緑色のUIViewのクラスを先ほどのReusableViewに変更しておきます。この段階で、一旦Doneです。XcodeをRunしてみます。

View1

変な顔。横にしてみます。

View2

ちゃんとAutolayoutも適用されているのがわかります。

さて、このビューの構造をRevealで見てみます。

Reveal

お分かりでしょうか?ReusableViewで指定したはずの緑色のビューが残っているように見えます。階層がひとつ余分にあるように思えます。これはその通りで、UIStoryboardでReusableViewにクラス指定したビューはインスタンス化される際に、Xibで作成したカスタムのビュー構造を読み込みません(デフォルトの振る舞いです)。インスタンス化される際に、読み込まないのですが、その後、initWithCoder:メソッドの中で、XibからViewのヒエラルキーをインスタンス化してその上に乗せるような形に実装しています。こういう構造なので一番下の緑色のViewを外すことはできません。

本当はこの緑色の部分がなくなってほしいのですが、これは健康的に諦めたほうが良さそうです(先駆者1の記事参照)。この部分をなくすこともできますが複雑な処理を挟む必要があり、将来的に問題を起こす可能性があると思うからです。

さて、後半ちょっと面倒な説明がありましたが、とりあえずXibで作ったViewをシンプルな構造を維持したままUIStoryboardで使い回しができるようになった!やった!ということにします。

次は、これをコードでも書きたい。というケースの説明をします。

先ほどのビューをコードでも使いたい

上述のコードを少し拡張します。ここは先駆者2の記事を参考にしています。

#import "ReusableView.h"

@implementation ReusableView

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    
    return self;
}

- (void)commonInit
{
    NSBundle *mainBundle = [NSBundle mainBundle];
    NSArray *loadedViews = [mainBundle loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
    UIView *loadedSubview = [loadedViews firstObject];
    
    [self addSubview:loadedSubview];
    
    loadedSubview.translatesAutoresizingMaskIntoConstraints = NO;
    
    [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeTop]];
    [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeLeft]];
    [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeBottom]];
    [self addConstraint:[self pin:loadedSubview attribute:NSLayoutAttributeRight]];
}

- (NSLayoutConstraint *)pin:(id)item attribute:(NSLayoutAttribute)attribute
{
    return [NSLayoutConstraint constraintWithItem:self
                                        attribute:attribute
                                        relatedBy:NSLayoutRelationEqual
                                           toItem:item
                                        attribute:attribute
                                       multiplier:1.0
                                         constant:0.0];
}

@end

こんな感じになります。コードを分離するために新たにCodeViewControllerというものを作りました。CodeViewControllerのコードはこのような感じになります。Autolayoutのところ変かもしれませんが、とりあえずコードでも書けることがわかりました。

#import "CodeViewController.h"
#import "ReusableView.h"

@interface CodeViewController ()

@end

@implementation CodeViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    ReusableView *rView = [[ReusableView alloc] initWithFrame:CGRectZero];
    [self.view addSubview:rView];
    [rView setTranslatesAutoresizingMaskIntoConstraints:NO];
    
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:rView
                                                          attribute:NSLayoutAttributeTop
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.topLayoutGuide
                                                          attribute:NSLayoutAttributeBottom
                                                         multiplier:1.0
                                                           constant:80.0]];
    
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:rView
                                                          attribute:NSLayoutAttributeLeft
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeLeft
                                                         multiplier:1.0
                                                           constant:100.0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:rView
                                                          attribute:NSLayoutAttributeRight
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeRight
                                                         multiplier:1.0
                                                           constant:-100.0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:rView
                                                          attribute:NSLayoutAttributeBottom
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.bottomLayoutGuide
                                                          attribute:NSLayoutAttributeTop
                                                         multiplier:1.0
                                                           constant:-80.0]];
}

@end

Github

コードはGithubにアップしておきます。将来的にパスが変更になったらごめんなさい。

Pocket
LINEで送る

You may also like...