web-dev-qa-db-ja.com

独自のXIBを持つUIViewサブクラス

カスタムUIViewサブクラスを作成しましたが、UIViewサブクラスのコードでUIをレイアウトしたくありません。そのためにxibを使用したいと思います。だから私がしたことは次のとおりです。

UIViewをサブクラス化するクラス「ShareView」を作成しました。ファイルの所有者を「ShareView」に設定してXIBファイルを作成しました。次に、「ShareView.h」で宣言したいくつかのアウトレットをリンクします。

次に、ShareViewをサブビューとして追加するViewController、MainViewControllerがあります。このコードで:

NSArray *arr = [[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:nil options:nil];
UIView *fv = [[arr objectAtIndex:0] retain];
fv.frame = CGRectMake(0, 0, 320, 407);
[self.view addSubview:fv];

しかし、ShareViewで宣言したアウトレットでNSUnknownKeyExceptionエラーが発生します。

私がこれをすべて行った理由は、個別のXIBファイルに独自のロジックを持つUIViewが必要だからです。 ViewControllersは全画面を管理するためにのみ使用される、つまり画面の一部ではないことをいくつかの場所で読みました...では、何が間違っているのでしょうか。 ShareViewのロジックを別のクラスに入れたいので、MainControllerクラスがShareViewのロジックで肥大化することはありません(これはこの問題を解決するための手段だと思いますか?)

21
ThomasM

ThomasM、

カスタムビュー内での動作のカプセル化についても同様のアイデアがありました(たとえば、最小/最大/現在の値のコンパニオンラベルが付いたスライダーで、値が変更されたイベントも内部でコントロールによって処理されます)。

現在のベストプラクティスでは、Eimantasが回答で説明しているように、Interface Builder(ShareView.xib)でShareViewを設計します。次に、ShareViewをMainViewController.xibのビュー階層に埋め込みます。

IOS開発者のブログに カスタムビューのペン先を他のペン先の中に埋め込む 方法を書きました。重要なのは、カスタムビューの-awakeAfterUsingCoder:をオーバーライドし、MainViewController.xibからロードされたオブジェクトを「埋め込み」ペン先(ShareView.xib)からロードされたオブジェクトに置き換えます。

これらの線に沿った何か:

// ShareView.m
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
    BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
    if (theThingThatGotLoadedWasJustAPlaceholder) {
        // load the embedded view from its Nib
        ShareView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([ShareView class]) owner:nil options:nil] objectAtIndex:0];

        // pass properties through
        theRealThing.frame = self.frame;
        theRealThing.autoresizingMask = self.autoresizingMask;

        [self release];
        self = [theRealThing retain];
    }
    return self;
}
26
Yang Meyer

ロードされたxibの所有者をnilとして定義しました。 xib自体のファイル所有者はアウトレットが接続されており、ShareViewのインスタンスとして定義されているため、不明なキーに関する例外が発生します(nilにはShareViewに対して定義したアウトレットプロパティがありません)。

Xibのローダーを所有者として定義する必要があります(つまり、xibのロードを担当するView Controller)。次に、別のUIViewオブジェクトをxibに追加し、それをShareViewのインスタンスとして定義します。次に、xibをロードするとき。

ShareView *shareView = [[[[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:self options:nil] objectAtIndex:0] retain];

ビューコントローラのインターフェイスでshareViewIBOutletとして定義することもできます(ファイル所有者からのアウトレットをxib自体のビューに接続します)。次に、xibをロードするときに、xibのロードプロセスによってビューがインスタンス変数に直接再接続されるため、shareViewインスタンス変数を再割り当てする必要はありません。

6
Eimantas

答えに付け加えたいと思います。しかし、人々がこの答えを改善してくれることを願っています。

まず第一に、それは機能します。

XIB:

enter image description here

結果:

enter image description here

特にtableViewCellについては、UIViewを長い間サブクラス化したいと思います。

これが私がやった方法です。

それは成功していますが、私の意見ではまだ「厄介」な部分があります。

まず、通常の.h、.m、およびxibファイルを作成しました。作成したサブクラスがUIViewControllerのサブクラスでない場合、Appleには、xibを自動的に作成するためのチェックボックスがないことに注意してください。とにかく作成してください。

#import <UIKit/UIKit.h>
#import "Business.h"

@interface BGUIBusinessCellForDisplay : UITableViewCell

+ (NSString *) reuseIdentifier;


- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz;
@end

本当に単純なUITableViewCellで、後者をbizで初期化したい。

UITableViewCellに対して実行する必要があるreuseidentifierを配置します

//#import "Business.h"
@interface BGUIBusinessCellForDisplay ()
@property (weak, nonatomic) IBOutlet UILabel *Title;
@property (weak, nonatomic) IBOutlet UIImageView *Image;
@property (weak, nonatomic) IBOutlet UILabel *Address;
@property (weak, nonatomic) IBOutlet UILabel *DistanceLabel;
@property (weak, nonatomic) IBOutlet UILabel *PinNumber;
@property (strong, nonatomic) IBOutlet BGUIBusinessCellForDisplay *view;

@property (weak, nonatomic) IBOutlet UIImageView *ArrowDirection;
@property (weak, nonatomic) Business * biz;
@end

@implementation BGUIBusinessCellForDisplay

- (NSString *) reuseIdentifier {
    return [[self class] reuseIdentifier];
};
+ (NSString *) reuseIdentifier {
    return NSStringFromClass([self class]);
};

次に、ほとんどの初期化コードを削除し、代わりにこれを配置しました。

- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz
{
    if (self.biz == nil) //First time set up
    {
        self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
        NSString * className = NSStringFromClass([self class]);
        //PO (className);
        [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
        [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
    }
    if (biz==nil)
    {
        return self;
    }

    self.biz = biz;
    self.Title.text = biz.Title; //Let's set this one thing first
    self.Address.text=biz.ShortenedAddress;

    //if([self.distance isNotEmpty]){
    self.DistanceLabel.text=[NSString stringWithFormat:@"%dm",[biz.Distance intValue]];
    self.PinNumber.text =biz.StringPinLineAndNumber;

それは本当に厄介であることに注意してください。

まず、initは2つの方法で使用できます。

  1. Alocの直後に使用できます
  2. これは、別の既存のクラスがあり、その既存セル​​を別のビジネスに初期化することで使用できます。

だから私はしました:

if (self.biz == nil) //First time set up
{
    self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
    NSString * className = NSStringFromClass([self class]);
    //PO (className);
    [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
    [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}

私がしたもう1つの厄介なことは、[self addSubview:self.view]を実行するときです。

事は私が自分自身になりたいということですtheビュー。 self.viewではありません。それにもかかわらず、どういうわけかそれは機能します。そうです、私が改善するのを手伝ってください、しかしそれは本質的にUIViewのあなた自身のサブクラスを実装する方法です。

4
user4951

IB_DESIGNABLEを使用して、xibで設計されたカスタムUIViewを作成し、InterfaceBuilderを作成して新しいXcode6の他のxibファイルまたはストーリーボード内に表示することもできます。 xibで、ファイル所有者をカスタムクラスに設定しますが、再通貨の読み込みの問題を回避するためにUIViewクラスを設定しないでください。デフォルトのUIViewクラスをそのままにしておくと、このUIViewをカスタムクラスビューのサブビューとして追加します。すべてのアウトレットをファイル所有者に接続し、カスタムクラスで以下のコードのようにxibをロードします。ここで私のビデオチュートリアルを確認できます: https://www.youtube.com/watch?v=L97MdpaF3Xg

IB_DESIGNABLE
@interface CustomControl : UIView
@end

@implementation CustomControl

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

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

- (void)load
{
    UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:@"CustomControl" owner:self options:nil] firstObject];
    [self addSubview:view];
    view.frame = self.bounds;
}

@end

自動レイアウトを使用している場合は、次のように変更することをお勧めします。view.frame = self.bounds; to:

[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
2
Leszek Szary

Auto-LayoutでYangのパターンを使用するには、-awakeWithCoder:メソッドのどこかに以下を追加する必要があります。

    theRealThing.translatesAutoresizingMaskIntoConstraints = NO;

-translatesAutoResizingMaskIntoConstraintsをオフにしないと、レイアウトが正しくなくなるだけでなく、コンソールで多くの意味のないデバッグが発生する可能性があります。

編集:自動レイアウトはまだ苦痛になる可能性があります。特定の制約は尊重されませんが、他の制約は尊重されます(たとえば、下部への固定は機能しませんが、上部への固定は機能します)。理由は正確にはわかりませんが、プレースホルダーからtheRealThingに手動で制約を渡すことで、これを回避できます。

このパターンは、通常の.xibの場合と同じようにストーリーボードでも機能することにも注意してください(つまり、.xibでUI要素を作成し、手順に従ってStoryBoard Viewコントローラーにドロップできます)。