カスタムiOSビュークラスを作成し、その複数のコピーをインスタンス化するにはどうすればよいですか(IBで)?


114

現在、基本的にすべて同じである複数のタイマーを備えたアプリを作成しています。

タイマーのすべてのコードとレイアウト/アニメーションを使用するカスタムクラスを作成したいので、互いに独立して動作する5つの同一のタイマーを持つことができます。

私はIB(xcode 4.2)を使用してレイアウトを作成し、タイマーのすべてのコードは現在、viewcontrollerクラスに含まれています。

すべてをカスタムクラスにカプセル化し、それをビューコントローラーに追加する方法に頭を悩ませています。何か助けていただければ幸いです。


ここにググる人のために... iOSにコンテナビューが登場しから5年になります!コンテナービューは、iOSで現在すべてを行う方法の基本的な中心的な考え方です。使い方は信じられないほど簡単で、コンテナビューに関する優れた(5歳の!)チュートリアルが数多くあります。たとえば
Fattie

2
@JoeBlow UITableViewCellまたはでコンテナビューを使用することはできませんUICollectionViewCell。私の場合、コントローラとコレクションビューで何度も使用できる、小さくてもかなり複雑なビューが必要です。そして、デザイナーはそれを作り直し続けるので、私はレイアウトが定義されている単一の場所が欲しいです。したがって、ペン先。
Echelon

回答:


142

概念的に答えると、タイマーはのサブクラスである可能性がUIViewありNSObjectます。

タイマーのインスタンスをIBでインスタンス化するUIViewには、ビューコントローラーのビューにドロップしてドロップし、そのクラスをタイマーのクラス名に設定します。

ここに画像の説明を入力してください

#importビューコントローラーのタイマークラスを忘れないでください。

編集:IB設計用(コードのインスタンス化については改訂履歴を参照)

私はストーリーボードについてはあまり詳しくありませんが.xib、ストーリーボードバージョンを使用するのとほぼ同じファイルを使用してIBでインターフェイスを構築できることは知っています。ビュー全体を既存のインターフェイスから.xibファイルにコピーして貼り付けることもできます。

これをテストするために、.xib「MyCustomTimerView.xib」という名前の新しい空を作成しました。次に、ビューを追加し、それにラベルと2つのボタンを追加しました。そのようです:

ここに画像の説明を入力してください

UIView「MyCustomTimer」という名前の新しいObjective-Cクラスサブクラスを作成しました。私.xibでは、ファイルの所有者クラスをMyCustomTimerに設定しました。これで、他のビュー/コントローラーと同じように、アクションとアウトレットを自由に接続できます。結果の.hファイルは次のようになります。

@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

ジャンプするために残っている唯一のハードルは、これ.xibを私のUIViewサブクラスで取得することです。.xib劇的に使用すると、必要なセットアップが削減されます。また、ストーリーボードを使用してタイマーをロードしている-(id)initWithCoder:ので、呼び出されるイニシャライザが唯一であることはわかっています。実装ファイルは次のようになります。

#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

名前の付いたメソッドloadNibNamed:owner:options:は、実際の動作とまったく同じです。Nibをロードし、「ファイルの所有者」プロパティをselfに設定します。配列の最初のオブジェクトを抽出します。これは、ニブのルートビューです。ビューをサブビューとして追加し、画面に表示します。

明らかに、これはボタンが押されたときにラベルの背景色を変更するだけですが、この例ではうまくいくはずです。

コメントに基づくメモ:

無限再帰の問題が発生している場合は、このソリューションの微妙なトリックを見逃している可能性があります。それはあなたが思っていることをやっていない。ストーリーボードに配置されたビューは表示されませんが、代わりに別のビューをサブビューとしてロードします。ロードするビューは、ペン先で定義されているビューです。nibの「ファイルの所有者」は、その目に見えないビューです。クールな部分は、この見えないビューが依然としてObjective-Cクラスであり、ペン先から取り込むビューの一種のビューコントローラーとして使用できることです。たとえばIBActionMyCustomTimerクラスのメソッドは、ビューよりもビューコントローラーで期待されるものです。

余談ですが、これはうまく機能しないと主張する人もいるでしょうMVC。私の観点から見るとUITableViewCell、これはカスタムとより密接に関連しており、カスタムパーツである場合もあります。

また、この回答は非常に具体的なソリューションを提供するためのものだったことにも注意してください。ストーリーボードにレイアウトされた同じビューで複数回インスタンス化できる1つのペン先を作成します。たとえば、iPadの画面上でこれらのタイマーのうち6つを一度に簡単に想像することができます。アプリケーション全体で複数回使用されるビューコントローラーのビューを指定する必要があるだけの場合、この質問に対してjyavenardが提供するソリューションは、ほとんどの場合、より良いソリューションです。


これは実際には別の質問のようです。しかし、私はそれはすぐに答えられると思うので、ここに行きます。新しい通知を作成するたびに、それは新しい通知ですが、同じ名前を付ける必要がある場合は、区別するために何かを追加したい場合はuserInfo、通知のプロパティを使用します@property(nonatomic,copy) NSDictionary *userInfo;
NJones

28
initWithCoderから無限再帰が発生します。何か案は?
消滅

6
古いスレッドを復活させて申し訳ありませんFile's Owner。クラス名に設定されていることを確認して、無限の再帰問題を解決しました。
themanatuf 2013年

20
無限再帰のもう1つの理由は、xibのルートビューをカスタムクラスに設定することです。あなたはそれをしたくないので、それをデフォルトの「UIView」のままにします
XY

11
このアプローチについて私の注意を引くのは1つだけです... 2つのビューが同じクラスにあることに誰も気になりませんか?最初のファイルであるUIViewから継承し[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; 、2番目のUIView を追加することで元のファイル.h / .mを継承してい ます。もしそうなら、これは設計によって作られたリンゴですか?または、これを行うことによって誰かが見つけた解決策ですか?
SH

137

Swiftの例

Xcode 10およびSwift 4用に更新

これが基本的なウォークスルーです。私はもともと、このYouTubeビデオシリーズを見て、多くのことを学びました。後で、この記事に基づいて回答を更新しました。

カスタムビューファイルを追加する

次の2つのファイルがカスタムビューを形成します。

  • レイアウトを含む.xibファイル
  • UIViewサブクラスとしての.swiftファイル

追加の詳細は以下のとおりです。

Xibファイル

プロジェクトに.xibファイルを追加します([ファイル]> [新規]> [ファイル...]> [ユーザーインターフェイス]> [表示])。私を呼んでいReusableCustomView.xibます。

カスタムビューに含めるレイアウトを作成します。例として、私は、レイアウトを行いますUILabelUIButton。自動レイアウトを使用して、後で設定したサイズに関係なく、自動的にサイズが変更されるようにすることをお勧めします。(属性インスペクターでxibサイズにフリーフォームを使用して、シミュレートされたメトリックを調整できるようにしましたが、これは必須ではありません。)

ここに画像の説明を入力してください

Swiftファイル

プロジェクトに.swiftファイルを追加します([ファイル]> [新規]> [ファイル...]> [ソース]> [Swiftファイル])。それはのサブクラスでUIViewあり、私は私を呼んでいReusableCustomView.swiftます。

import UIKit
class ResuableCustomView: UIView {

}

Swiftファイルを所有者にする

.xibファイルに戻り、ドキュメントアウトラインの[ファイルの所有者]をクリックします。Identity Inspectorで、.swiftファイルの名前をカスタムクラス名として書き込みます

ここに画像の説明を入力してください

カスタムビューコードを追加

ReusableCustomView.swiftファイルの内容を次のコードに置き換えます。

import UIKit

@IBDesignable
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView:UIView?

    @IBOutlet weak var label: UILabel!

    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    func commonInit() {
        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

必ず、.xibファイルの名前の正しいスペルを取得してください。

アウトレットとアクションを接続する

コントロールをxibレイアウトのラベルとボタンから迅速なカスタムビューコードにドラッグして、アウトレットとアクションを接続します。

カスタムビューを使用する

これでカスタムビューが完成しました。あなたがしなければならないすべてはあなたがUIViewそれをあなたのメインの絵コンテの好きな場所に追加することです。ReusableCustomViewIdentity Inspectorでビューのクラス名をに設定します。

ここに画像の説明を入力してください


31
XibビュークラスをResuableCustomViewとして設定するのではなく、このXibの所有者として重要です。そうしないと、エラーが発生します。
RealNmae

5
はい、良いリマインダーです。Xibのビュー(ファイル所有者ではなく)をクラス名に設定することによって引き起こされる無限再帰の問題を解決するためにどれだけの時間を費やしたかわかりません。
Suragch

2
@TomCalmon、私はこのプロジェクトをXcode 8でSwift 3を使用してやり直しました。
Suragch 2016

6
他の誰かがIBでのビューのレンダリングに問題がある場合は、に変更Bundle.mainするとBundle(for: ...)うまくいくことがわかりました。どうやら、Bundle.mainデザイン時には利用できません。
crizzis

4
これが@IBDesignableで機能しない理由は、実装していないためinit(frame:)です。これを追加すると(すべての初期化コードをいくつかのcommonInit()関数にプルすると)、正常に機能し、IBに表示されます。ここで詳細情報を参照してください。stackoverflow.com/questions/31265906/...
ディミトリス

39

ビューではなく、ビューコントローラに対する回答:

ストーリーボードからxibをロードする簡単な方法があります。コントローラがから継承するMyClassControllerタイプであるとしましょうUIViewController

UIViewControllerストーリーボードに使用IBを追加します。クラスタイプをMyClassControllerに変更します。ストーリーボードに自動的に追加されたビューを削除します。

呼び出すXIBがMyClassController.xibであることを確認してください。

ストーリーボードの読み込み中にクラスがインスタンス化されると、xibが自動的に読み込まれます。これはUIViewController、クラス名で指定されたXIBを呼び出すデフォルトの実装が原因です。


1
私はいつもカスタムビューを膨らませていますが、ストーリーボードに読み込むためのこの小さなトリックを知りませんでした。とても有難い!
MrTristan 2014

38
問題は、ビューコントローラではなく、ビューに関するものです。
リカルド

明確化のためにタイトルを追加しました。「ビューではなくビューコントローラに答える:」
nacho4d 2015年

1
iOS 9.1では動作していないようです。自動作成された(デフォルト)ビューを削除すると、VCのviewDidLoadから戻った後にクラッシュします。XIBのビューがデフォルトのビューの代わりに接続されるとは思いません。
ジェフ

1
私が得ている例外はです'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''。whacko nibNameに注意してください。nibの正しいクラス名を使用しています。
ジェフ

16

これは正解ではありませんが、このアプローチを共有することは有益だと思います。

Objective-C

  1. CustomViewWithXib.hCustomViewWithXib.m をプロジェクトにインポートします
  2. 同じ名前のカスタムビューファイルを作成します(.h / .m / .xib)
  3. CustomViewWithXibからカスタムクラスを継承する

迅速

  1. CustomViewWithXib.swiftをプロジェクトにインポートします
  2. 同じ名前のカスタムビューファイルを作成します(.swiftと.xib)
  3. CustomViewWithXibからカスタムクラスを継承する

オプション:

  1. 一部の要素を接続する必要がある場合は、xibファイルに移動し、所有者にカスタムクラス名を設定します(詳細については、「Swiftファイルを@Suragch回答の所有者にする」を参照してください)。

これですべてです。カスタムビューをストーリーボードに追加すると、表示されます:)

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)
    }
}

ここにいくつかの例があります

お役に立てば幸いです。


ビューuicontrolにiboutletを追加する方法
Amr Angry

1
@AmrAngryさん、ビューを接続するには、4つのステップを実行する必要があります。xibファイルに移動し、「ファイルの所有者」でクラスを設定し、「ctrl」をタップしてビューをインターフェースにドラッグアンドドロップします。この例のコードソースは、何か質問があれば遠慮なく、それが役立つことを願っています
HamzaGhazouani '25

あなたのリプレイに感謝します、私はすでにこれをしていますが、この部分では私の問題はないと思います。私の問題は、UIDatePIckerを追加し、ViewControllerからこのコントロールのターゲットアクションを追加して設定し、uiControleerイベントの値が変更されたが、メソッドが呼び出されない/起動しない
Amr Angry

1
私はピッカービューを追加することにより、例を更新@AmrAngry、それはあなたを助けるかもしれ:) github.com/HamzaGhazouani/Stackoverflow/tree/master/...
HamzaGhazouani

1
ずっと探していてくれてありがとう。
MH175
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.