UIViewサブクラスのNibをロードする正しい方法


98

この質問が以前に尋ねられたことは承知していますが、答えは矛盾していて混乱していますので、私を怒らせないでください。

UIViewアプリ全体で再利用可能なサブクラスを使用したいと考えています。nibファイルを使用してインターフェイスを記述したいと思います。

ここで、アクティビティインジケーターを含む読み込みインジケータービューであるとしましょう。いくつかのイベントで、このビューをインスタンス化し、ビューコントローラーのビューにアニメーション化したいと思います。プログラムでビューのインターフェイスを問題なく説明し、要素をプログラムで作成し、フレームをinitメソッド内に設定することなどができます。

どうすればnibを使用してこれを行うことができますか?フレームを設定せずに、インターフェイスビルダーで指定されたサイズを維持します。

私はなんとかこうやってそれをすることができました、しかし私はそれが間違っていると確信しています(それはその中にピッカーを持つ単なるビューです):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

しかし、私は自分を上書きしています。それをインスタンス化すると、次のようになります。

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

フレームをIBからピックアップするのではなく、手動で設定する必要があります。

編集:ビューコントローラーでこのカスタムビューを作成し、ビューの要素を制御するアクセス権を取得したいと考えています。新しいView Controllerは欲しくない。

ありがとう

編集:これがベストプラクティスかどうかはわかりませんが、そうでないと確信していますが、これは私がやった方法です:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

正しい練習はまだ欲しかった

これは、ビューコントローラーを使用して行われ、nib名で初期化されている必要がありますか?コードの一部のUIView初期化メソッドでnibを設定する必要がありますか?それとも私がやったことは大丈夫ですか?


しかし、コードの最初の行は、元の質問で使用したコードとほとんど同じではありませんinitWithDataSourceか?とにかく、所有者に「Nil」を指定してもこれは機能します。
Rakesh 2013年

最近の99.99999%のケースでは、コンテナービューを使用しているだけであることは注目に値しますコンテナービューは現在、iOSのあらゆる場所で常に使用されています。 ハウツー記事
Fattie、2015

[NSString stringWithFormat:@ "%@"、[FSASelectView class]]をNSStringFromClass([FSASelectView class])で置き換えることができることに注意してください
naomimichiko

回答:


132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

これを使用して、再利用可能なカスタムビューを初期化しています。


最後の"firstObject"を使用できることに注意してください。少しすっきりしています。「firstObject」は、NSArrayおよびNSMutableArrayの便利なメソッドです。

以下は、テーブルヘッダーとして使用するxibをロードする典型的な例です。ファイルYourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

通常、では、TopArea.xib[ファイル所有者]をクリックして、ファイル所有者をYourClassに設定します。次に、実際にはYourClass.hに IBOutletプロパティがあります。ではTopArea.xib、コントロールをそれらのアウトレットにドラッグできます。

でのことを忘れないでください。必要に応じて、ビュー自体TopArea.xibクリックしていくつかのアウトレットにドラッグする必要がある場合があります。(非常に価値のあるヒントは、これをテーブルセル行に対して行う場合は、絶対にそれを行う必要があることです。コード自体で、ビュー自体を関連するプロパティに接続する必要があります。)


UIViewにはinitWithNibName:メソッドはありません。UIViewControllerのためだけです
meronix

これはビューコントローラー用です。UIViewを使用して、それを作成するコントローラーを所有します。
アダムウェイト2013年

すみません、急いで間違ったコードをコピーしました。答えを更新しました。ご覧ください。
Premsuraj 2013年

これを正しいとマークします。それがベストプラクティスかどうかはわかりませんが、うまくいきます。
アダムウェイト2013年

@Joe Blowバンプでごめんなさい。でも、なぜこれをしなければならないのでしょうか。 、 必要であれば。" 代わりに、myViewObjectを使用して、UIView自体である基になるビューにアクセスできますか?プロパティが必要なのはなぜですか?
user1686342 2015

39

CustomViewとをxib独立させたい場合はFile's Owner、次の手順に従ってください

  • File's Ownerフィールドは空のままにします。
  • xibファイルで実際のビューをクリックしCustomView、それCustom ClassCustomView(カスタムビュークラスの名前)として設定します。
  • カスタムビューのファイルを追加IBOutlet.hます。
  • .xibカスタムビューのファイルは、ビューをクリックして中に入りますConnection Inspector。ここでは、.hファイルで定義したすべてのIBOutletを使用します
  • それらをそれぞれのビューに接続します。

.mあなたのファイルCustomView、クラス、オーバーライドしinit、次のようにメソッドを

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

をロードするCustomView場合は、次のコード行を使用します [[CustomView alloc] init];


1
iOS 9の時点で、これはこのページにリストされている唯一のソリューションであり、実際に機能します(他のソリューションが原因でクラッシュします)。
lifjoy 2016

このアプローチは呼び出さないため、既存のXIBからカスタムビューをロードすることと互換性がないことに注意してください-init。代わりに、それが呼び出さinitWithFrame:-awakeFromNibます。-initメソッドと同じコードを記述してビューをロードすると、無限ループに入ります。
Dustt

25

次の手順に従ってください

  1. タイプのMyView .h / .mという名前のクラスを作成しますUIView
  2. 同じ名前のxibを作成しますMyView.xib
  3. 次に、ファイル所有者クラスをxib UIViewControllerからNSObjectに変更します。下の画像をご覧ください ここに画像の説明を入力してください
  4. ファイル所有者ビューを自分のビューに接続します。下の画像をご覧ください ここに画像の説明を入力してください

  5. ビューのクラスをに変更しますMyView。3と同じ。

  6. 配置コントロールはIBOutletsを作成します。

ビューをロードするコードは次のとおりです。

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

それが役に立てば幸い。

明確化

UIViewControllerxibをロードするために使用され、ビューUIViewControllerは実際MyViewにMyView xibで割り当てたものです。

デモここで デモグラブを作成しました


なぜ人々はこれを間違って投票するのですか?まだ試していませんが、思ったようにはいかないようです。
アダムウェイト2013年

私は反対票を投じていましたが、それが原因であなたの答えを誤って読みました(ファイルの所有者としてUIViewサブクラスを割り当てていたと思います)。申し訳ありません。
Rakesh 2013年

2
xibファイルで、ファイルの所有者を無視します。次に、MyView * view = [[UINib instantiateWithOwner:nil options:nil] lastObject];を実行するだけで、UIViewControllerを作成する必要を回避できます。
LightningStryk

1
@LightningStrykはい、もちろんですが、それはそれを行う別の方法にすぎません。
イフォニック2014年

1
これは、再利用可能なUIViewサブクラスを作成するためのものであり、その目的でUIViewControllerを使用するためのものではありません。
arielyz 2014年

11

ここで2年か数年後の自分の質問に答えますが...

それはプロトコル拡張を使用するので、すべてのクラスのための追加のコードなしでそれを行うことができます。

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()

8

Swiftの場合:

たとえば、カスタムクラスの名前は InfoView

最初に、あなたはファイルを作成InfoView.xibし、InfoView.swiftこのように:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

次にFile's Owner、次のUIViewControllerように設定します。

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

の名前をViewに変更InfoView

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

を右クリックしFile's Ownerて、viewフィールドを以下と接続しますInfoView

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

クラス名がInfoView

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

その後、カスタムクラスのボタンにアクションを問題なく追加できます。

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

そしてあなたのこのカスタムクラスの使い方MainViewController

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}

2

さて、あなたはビューコントローラを使用してxibを初期化し、viewController.viewを使用することができます。またはそれをあなたがした方法で行います。UIViewサブクラスをコントローラとしてのみ作成することUIViewは悪い考えです。

カスタムビューからアウトレットがない場合は、UIViewControllerクラスを直接使用して初期化できます。

更新:あなたの場合:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

それ以外の場合は、カスタムを作成する必要がありますUIViewController(コンセントが適切に配線されるように、ファイルの所有者としてそれを作成するため)。

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

使用できるビューを追加したいところはどこでも。

[parentView addSubview:yCustCon.view];

ただし、xibのロード中に別のビューコントローラー(既に別のビューで使用されている)を所有者として渡すことは、コントローラーのビュープロパティが変更され、元のビューにアクセスするときに、それへの参照があります。

編集:ファイルの所有者を同じメインUIViewControllerクラスとして新しいxibをセットアップし、ビュープロパティを新しいxibビューに関連付けた場合、この問題に直面します。

すなわち;

  • YourMainViewController-管理-mainView
  • CustomView-必要に応じてxibからロードする必要があります。

以下のコードは、ビューのロードがの内部で記述した場合、後で混乱を招きますYourMainViewController。これはself.view、この時点からカスタムビューを参照するためです

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}

基本的に、ベストプラクティスでは、すべてのUIViewに関連付けられたコントローラーが必要であるとしています
アダムウェイト2013年

必ずしも。しかし、別のxibからビューをロードする場合、それは問題のない方法です。IOS5より前のアップルドキュメントでは、1つのビューコントローラを使用して複数のシーン(xib)を制御することはできません(逆も同様)。
Rakesh 2013年

xibは実際にはアラートビューのサイズにすぎません
Adam Waite

個人的には、新しいコントローラーを作成する必要があるかどうかは、コード/ xibの複雑さに依存します。正常に発生した問題を処理でき、将来の変更を検討していない場合は問題ありません。しかし、別のビューコントローラーを作成すると、多くの問題が解決します。そして、あなたの場合、それは単なるアクティビティインジケータなので、UIViewController(回答で述べたように)オブジェクトを簡単に使用できます。これを行うために私の答えを更新します。
Rakesh 2013年

1
そのため、プログラムで作成するのではなく、インターフェイスビルダーでインターフェイスを描画できます。プログラムでそれを行うことは問題ではありません。UIViewをサブクラス化し、ペン先を与える方法を知りたいだけです。これは難しいことではありません。
アダムウェイト2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.