iOS 使用自定义 Xib 和视图覆盖 initWithFrame 的正确方法?

Posted

技术标签:

【中文标题】iOS 使用自定义 Xib 和视图覆盖 initWithFrame 的正确方法?【英文标题】:iOS Proper Way To Override initWithFrame With Custom Xib and View? 【发布时间】:2014-07-21 20:06:38 【问题描述】:

现在,我对我为尝试创建自定义视图而创建的这个 hacky 解决方法不太满意。它在另一个视图控制器中使用 initWithFrame 以编程方式初始化,因此我在代码中覆盖了它。这似乎不是初始化我的视图的正确方法,但我不确定还能做什么。

- (instancetype)initWithFrame:(CGRect)frame 
    self = [super initWithFrame:frame];
    if (self) 
        self = [[[NSBundle mainBundle] loadNibNamed:@"OTGMarkerDetailView" owner:self options:nil] lastObject];
        [self setFrame:frame];
    
    return self;

如果没有 setFrame 方法,自定义视图似乎总是在窗口顶部创建,而不是在我使用 CGRectMake 指定的坐标处。

我在这里用视图控制器中的方法初始化视图。

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker 
    if (!self.detailView) 
        self.detailView = [[OTGMarkerDetailView alloc] initWithFrame:CGRectMake(0, 568, 320, 55)];
    

    [self.view addSubview:self.detailView];
    self.detailView.backgroundColor = [UIColor whiteColor];

    NSRange range = [self.startAddress rangeOfString:@","];
    NSString *mainAddress = [self.startAddress substringToIndex:range.location];
    NSString *subAddress = [self.startAddress substringFromIndex:range.location + 1];
    [self.detailView setLabelsWithMainAddress:mainAddress subAddress:subAddress];

    [UIView animateWithDuration:0.5 animations:^
        self.detailView.frame = CGRectMake(0, 513, 320, 55);
    ];

    return YES;

【问题讨论】:

【参考方案1】:

一般来说,处理此类事情的最佳方式是使用类工厂方法:

+(instancetype)markerDetailView 
    [[[NSBundle mainBundle] loadNibNamed:@"OTGMarkerDetailView" owner:nil options:nil] lastObject];

然后你创建视图:

self.detailView = [OTGMarkerDetailView markerDetailView];

请注意,我也更喜欢采用更安全的方法从 nib 文件加载视图:

+(instancetype)markerDetailView 
    for(OTGMarkerDetailView* view in [[NSBundle mainBundle] loadNibNamed:@"OTGMarkerDetailView" owner:nil options:nil]) 
        if([view isKindOfClass:[OTGMarkerDetailView class]]) 
            return view;
        
    

    NSAssert(false, @"Failed to load OTGMarkerDetailView");
    return nil;

如果您需要在 InterfaceBuilder 中使用自定义视图的实例,您必须覆盖 initWithCoder:initWithFrame:。既然你要替换自己,真的没有必要打电话给super

-(id)initWithCoder:(NSCoder*)aDecoder 
    self = [[self class] markerDetailView];
    // I think this will work to inherit settable properties in IB
    if((self = [super initWithCoder:aDecoder])) 
        // support any additional settable properties in IB
    
    return self;

【讨论】:

如果我需要在界面生成器中创建它怎么办?我是否应该将 self 设置为 markerDetailView 的返回值,因为我必须重写 initWithCoder 和 initWithFrame? 是的,如果您想从 Interface Builder 实例化自定义类的实例并通过从 ni​​b 文件加载来创建自定义视图,那么您必须覆盖 initWithCoder 和 initWithFrame,但是在这两种情况下,您都不会打扰调用 super ,因为无论如何您都要把它扔掉。 在这种情况下,我可能仍会使用工厂方法,并将其包装在 initWithCoder 和 initWithFrame 中。 我看到了一些示例,他们在自定义初始化程序的末尾执行 [self addSubview:customView]。如果我可以将 self 设置为视图本身,是否有任何理由这样做? 向前迈进,IB 6 和它对 IBDesignable 的支持,你可能想要避免这个比喻,但我不确定这将如何真正发挥作用。部分原因是我现在在代码中为我的自定义视图创建子视图,尽管我不喜欢这样做。【参考方案2】:

为什么mapView 方法不做你的init 方法做的事情,即直接将视图拉出它的笔尖?换句话说,你为什么要对mapView 隐瞒获取该视图实例的方法是加载一个笔尖这一事实?没有理由隐瞒这个事实。

或者,如果您确实想隐藏这个事实,为什么不编写一个方便的构造函数作为视图的类方法,而不是滥用initWithFrame?例如[OTGMarkerDetailView makeADetailView]...

【讨论】:

没有理由这样做。我会解决这个问题 - 但至于其他一切,这是创建自定义视图的可接受方式吗? 一般来说...没有。您正在初始化您的自定义类,然后通过为 self 分配一个不同的实例立即将其丢弃。更好的解决方案是从 nib 文件加载实例的类工厂方法。 对,@David,这就是我所说的便利构造函数作为类方法的意思。 我主要是在回答他关于这是一种合理方法的问题,完全错过了您的构造函数评论。 是的,我明白,我喜欢你的回答。我只是在澄清我们同意的 OP。

以上是关于iOS 使用自定义 Xib 和视图覆盖 initWithFrame 的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章

iOS:使用 xib 的自定义视图

如何:在iOS中使用StackView View从XIB自我调整自定义视图

从 xib 加载 iOS 不起作用

iOS 8 自定义键盘方向更改

Xcode:如何在 UIScrollView 中从 XIB 文件添加自定义视图

自定义 UIView XIB 内的 iOS 导航返回按钮(无导航控制器)