web-dev-qa-db-ja.com

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

現在、複数のタイマーを使用するアプリを作成していますが、これらは基本的にすべて同じです。

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

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

すべてをカスタムクラスにカプセル化し、それをViewControllerに追加する方法について頭を包むのが困難です。どんな助けでも大歓迎です。

104

概念的に答えると、タイマーはおそらくUIViewではなくNSObjectのサブクラスである必要があります。

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

enter image description here

View Controllerでタイマークラスを#importすることを忘れないでください。

編集:IBデザインの場合(コードのインスタンス化の場合は改訂履歴を参照)

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

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

enter image description here

「MyCustomTimer」という名前のUIViewをサブクラス化する新しいObjective-Cクラスを作成しました。 .xibFile's Ownerクラスを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

ジャンプする唯一のハードルは、UIViewサブクラスでこの.xibを取得することです。 .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:という名前のメソッドは、まさにそのように聞こえます。ペン先をロードし、「File's Owner」プロパティをselfに設定します。配列の最初のオブジェクトを抽出します。これは、ペン先のルートビューです。ビューをサブビューとして追加し、画面に表示します。

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

コメントに基づく注記:

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

サイドノートとして、これがMVCを壊すと主張する人もいるかもしれませんが、私はいくらか同意します。私の観点から見ると、カスタムのUITableViewCellにもっと密接に関係しています。これは、一部コントローラーである必要がある場合もあります。

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

139
NJones

迅速な例

Xcode 10およびSwift 4向けに更新

基本的な手順を次に示します。私はもともとこれをたくさん見ました このYoutubeビデオシリーズ 。後で この記事 に基づいて回答を更新しました。

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

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

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

それらを追加するための詳細は以下です。

Xibファイル

プロジェクトに.xibファイルを追加します(File> New> File ...> User Interface> View)。私はReusableCustomView.xibと呼んでいます。

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

enter image description here

Swiftファイル

プロジェクトに.Swiftファイルを追加します(File> New> File ...> Source> Swift File)。これはUIViewのサブクラスであり、私はReusableCustomView.Swiftと呼んでいます。

import UIKit
class ResuableCustomView: UIView {

}

Swiftファイルを所有者にします

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

enter image description here

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

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レイアウトのラベルとボタンからSwiftカスタムビューコードにコントロールをドラッグして、アウトレットとアクションを接続します。

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

カスタムビューが完成しました。必要なことは、メインストーリーボードのどこにでもUIViewを追加することです。 Identity Inspectorでビューのクラス名をReusableCustomViewに設定します。

enter image description here

120
Suragch

View Controllerに対する回答、ビューではない

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

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

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

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

39
jyavenard

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

Objective-C

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

Swift

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

いくつかの例を見つけることができます こちら

お役に立てば幸いです。

15
Hamza