4

カスタム ビューの現在のベスト プラクティスは次のとおりです。

  1. Nib でカスタム ビューを構築します。
  2. ビュー コントローラーで、Nib をプログラムで読み込み、読み込まれたオブジェクトの配列からカスタム ビューを取得します (これは UIView カテゴリ メソッドで行います+loadInstanceFromNib)。
  3. カスタム ビューをサブビューとして追加し、そのフレームを設定します。

私たちが実際に望んでいるのは、カスタム ビュー Nib をビュー コントローラー Nib 内に「埋め込む」ことです。それができない場合は、少なくともビュー コントローラー Nib 内にカスタム ビュー インスタンスを追加して配置したいと考えています (その内容は表示されません)。

次の解決策に非常に近づいています。

@implementation CustomView

static BOOL loadNormally;

- (id) initWithCoder:(NSCoder*)aDecoder {
    id returnValue = nil;
    if (loadNormally) { // Step 2
        returnValue = [super initWithCoder:aDecoder];
        loadNormally = !loadNormally;
    } else {            // Step 1
        loadNormally = !loadNormally;
        returnValue = [CustomView loadInstanceFromNib];
    }
    return returnValue;
}

- (id) initWithFrame:(CGRect)frame {
    loadNormally = YES;
    self = (id) [[CustomView loadInstanceFromNib] retain];
    self.frame = frame;
    return self;
}
// ...
@end

プログラムでカスタム ビューをインスタンス化する場合は-initWithFrame:、Nib からビューをロードし ( -initWithCoder:「ステップ 2」というラベルの付いた if-branch を呼び出して直接移動します)、フレームを設定し、保持カウントを 1 に設定する を使用します。

ただし、ビュー コントローラーの Nib 内でカスタム ビューをインスタンス化する場合、(確かにやや醜い) 静的loadNormally変数は最初はNO次のようになります。「ステップ 1」から開始します。の「通常の」if-ブランチをすぐに使用し-initWithCoder:ます。カスタム ビュー Nib からのロードは、-initWithCoder:今度はに戻ることを意味しloadNormally==YESます。つまり、Nib ロード メカニズムにその仕事をさせて、カスタム ビュー インスタンスを返します。

結果の要約:

  • 良い点:動作します!!! Interface Builder には「プラグ可能な」カスタム ビューがあります。
  • 悪い点:醜い静的変数… :-/
  • 醜い: カスタム ビューのインスタンスがリークされました! これは私があなたの助けが欲しいところです - 私は理由を理解していません. 何か案は?
4

3 に答える 3

3

-awakeAfterUsingCoder:最終的には、ビュー コントローラーの Nib から読み込まれたオブジェクトを、"埋め込まれた" Nib (CustomView.xib) から読み込まれたオブジェクトに置き換えて、カスタム ビューでオーバーライドするという、より良い方法にたどり着きました。

カスタム ビュー Nib を他の Nib 内に埋め込む方法については、詳細なブログ投稿で説明しました。

コードは次のようになります。

// CustomView.m
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
    BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
    if (theThingThatGotLoadedWasJustAPlaceholder) {
        // load the embedded view from its Nib
        CustomView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([CustomView 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;
}
于 2011-07-20T12:09:29.777 に答える
1

ヤンの答えは素晴らしいです...しかし、「割り当て解除されたインスタンスに送信されるメッセージ」は引き続き発生する可能性があります。「自己」割り当てを使用してこの問題を解決しました。

したがって、ARC を使用する場合は、この「自己」割り当てを許可する必要があります。(詳細については、 https://blog.compeople.eu/apps/?p=142を参照してください)

ARC プロジェクトでこれを実現するには、ファイルに「-fno-objc-arc」フラグ コンパイラ設定を追加します。次に、このファイルで NO-ARC コーディングを行います (dealloc 設定 nils、スーパー dealloc の呼び出しなど)。

また、クライアント nib の viewcontroller は、強いプロパティを使用して、awakeFromNib によって返されるインスタンスを保持する必要があります。私のサンプル コードの場合、customView は次のように参照されます。


@property ( strong , nonatomic) IBOutlet CustomView* customView;


最後に、 UIView+Utilカテゴリで定義されたcopyUIPropertiesTo:loadNibNamedを使用して、プロパティの処理と nib の読み込みにいくつかの改善を加えました。

だからawakeAfterUsingCoder:コードは今

#import "UIView+Util.h"
...
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder
{
    // are we loading an empty “placeholder” or the real thing?
    BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);

    if (theThingThatGotLoadedWasJustAPlaceholder)
    {
        CustomView* customView = (id) [CustomView loadInstanceFromNib];
        // copy all UI properties from self to new view!
        // if not, property that were set using Interface buider are lost!
        [self copyUIPropertiesTo:customView];

        [self release];
        // need retain to avoid deallocation
        self = [customView retain];
    }
    return self;
}

UIView+Util カテゴリ コードは

@interface UIView (Util)
   +(UIView*) loadInstanceFromNib;
   -(void) copyUIPropertiesTo:(UIView *)view;
@end

その実装とともに

#import "UIView+Util.h"
#import "Log.h"

@implementation UIView (Util)

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

-(void) copyUIPropertiesTo:(UIView *)view
{
    // reflection did not work to get those lists, so I hardcoded them
    // any suggestions are welcome here

    NSArray *properties =
    [NSArray arrayWithObjects: @"frame",@"bounds", @"center", @"transform", @"contentScaleFactor", @"multipleTouchEnabled", @"exclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"opaque", @"clearsContextBeforeDrawing", @"hidden", @"contentMode", @"contentStretch", nil];

    // some getters have 'is' prefix
    NSArray *getters =
    [NSArray arrayWithObjects: @"frame", @"bounds", @"center", @"transform", @"contentScaleFactor", @"isMultipleTouchEnabled", @"isExclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"isOpaque", @"clearsContextBeforeDrawing", @"isHidden", @"contentMode", @"contentStretch", nil];

    for (int i=0; i<[properties count]; i++)
    {
        NSString * propertyName = [properties objectAtIndex:i];
        NSString * getter = [getters objectAtIndex:i];

        SEL getPropertySelector = NSSelectorFromString(getter);

        NSString *setterSelectorName =
            [propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] capitalizedString]];

        setterSelectorName = [NSString stringWithFormat:@"set%@:", setterSelectorName];

        SEL setPropertySelector = NSSelectorFromString(setterSelectorName);

        if ([self respondsToSelector:getPropertySelector] && [view respondsToSelector:setPropertySelector])
        {
            NSObject * propertyValue = [self valueForKey:propertyName];

            [view setValue:propertyValue forKey:propertyName];
        }
    }    
}
于 2012-06-07T15:10:05.023 に答える