为UIView子类加载Nib的正确方法


98

我知道以前曾问过这个问题,但答案是矛盾的,我很困惑,所以请不要解雇我。

我希望UIView在我的整个应用程序中都有一个可重用的子类。我想用一个nib文件描述接口。

现在,我们说这是一个带有活动指示器的加载指示器视图。我想在某个事件上实例化此视图并为视图控制器的视图添加动画。我可以以编程方式描述视图的界面,以编程方式创建元素并在init方法中设置其框架等。

但是,如何使用笔尖呢?保持界面生成器中给定的大小,而无需设置框架。

我已经设法做到了,但是我确定这是错误的(只是一个带有选择器的视图):

 - (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拿起它。

编辑:我想在视图控制器中创建此自定义视图,并有权控制视图的元素。我不需要新的视图控制器。

谢谢

编辑:我不知道这是否是最佳实践,我敢肯定这不是最佳实践,但这是我的方法:

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) {
                        }];

仍然需要正确的做法

是否应该使用视图控制器并以笔尖名称初始化init?我应该在代码中的某些UIView初始化方法中设置笔尖吗?还是我做的还好吗?


但是第一行代码与您在原始问题中使用的代码几乎不相同initWithDataSource吗?无论如何,即使您将所有者设置为“无”,这也将起作用。
Rakesh 2013年

值得注意的是,如今在99.99999%的情况下,人们只会使用容器视图,而容器视图现在在iOS中随处可见,并且始终在使用。 操作方法文章
Fattie 2015年

旁注,您可以将[NSString stringWithFormat:@“%@”,[FSASelectView class]]替换为NSStringFromClass([FSASelectView class])
naomimichiko

Answers:


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,您将单击File Owner并将文件所有者设置为YourClass。然后实际上在YourClass.h中,您将具有IBOutlet属性。在中TopArea.xib,您可以将控件拖到这些出口。

别忘了在中TopArea.xib,您可能必须单击“视图”本身并将其拖动到某个出口,因此如有必要,您可以对其进行控制。(一个非常有价值的提示是,当您对表格单元格行执行此操作时,绝对必须这样做-您必须将视图本身连接到代码中的相关属性。)


没有用于UIView的initWithNibName:方法,仅用于UIViewController
meronix 2013年

那是一个视图控制器,我想使用UIView并让创建它的控制器拥有它。
亚当·怀特

抱歉,我匆忙复制了错误的代码,我已经更新了答案,请看看。
2013年

我将标记为正确。我不知道这是否是最佳做法,但可以。
亚当·怀特

@Joe Blow对不起,但是为什么要这样做:“别忘了在TopArea.xib中,您可能必须单击View本身并将其拖动到某个出口,因此您可以控制它,如有必要。” 您是否可以仅使用myViewObject来访问基础视图,因为它本身就是UIView?为什么需要财产?
user1686342 2015年

39

如果您想CustomView使其xib独立于File's Owner,请按照以下步骤操作

  • 将该File's Owner字段留空。
  • 单击xib您的文件中的实际视图,CustomView并将其设置Custom ClassCustomView(您的自定义视图类的名称)
  • IBOutlet.h您的自定义视图的文件中添加。
  • .xib您的自定义视图文件中,单击视图并进入Connection Inspector。在这里,您将在.h文件中定义的所有IBOutlets
  • 将它们与各自的视图连接起来。

.mCustomView班级的文件中,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

请注意,此方法与不调用从现有的XIB加载自定义视图不兼容-init。相反,它将调用initWithFrame:-awakeFromNib。如果您使用与方法中相同的代码来加载视图,-init则将进入无限循环。
尘土

25

请遵循以下步骤

  1. 创建一个名为MyView .h / .m的类UIView
  2. 创建一个同名的xib MyView.xib
  3. 现在UIViewControllerNSObject在xib中将File Owner类更改为from 。见下图 在此处输入图片说明
  4. 将文件所有者视图连接到您的视图。见下图 在此处输入图片说明

  5. 将您的视图的类更改为MyView。与3相同

  6. 放置控件创建IBOutlets。

这是加载视图的代码:

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

希望能帮助到你。

澄清

UIViewController用于加载xib和MyView xib中UIViewController实际MyView分配的View。

演示 我已经在这里进行了演示


人们为什么不赞成这一点呢?我还没有尝试过,但这看起来并不能满足我的要求。
亚当·怀特

我投票否决了,但是那导致我误读了您的答案(以为您将UIView子类分配为文件的所有者)。对于那个很抱歉。
拉克什

2
在您的xib文件中,忽略文件所有者。然后,您可以避免仅通过执行MyView * view = [[UINib InstantiateWithOwner:nil options:nil] lastObject]来创建UIViewController的方法。
LightningStryk

1
@LightningStryk当然可以,但这只是另一种方式。
Iphonic 2014年

1
用于创建可重用的UIView子类,而不是为此目的使用UIViewController。
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.xibInfoView.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 OwnerUIViewController这样的:

在此处输入图片说明

将您的重命名ViewInfoView

在此处输入图片说明

右键单击,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时将另一个视图控制器(已经用于另一个视图)作为所有者不是一个好主意,因为控制器的view属性将被更改,并且当您要访问原始视图时,您不会有一个参考。

编辑:如果您将新的xib与文件的所有者设置为相同的主UIViewController类,并将view属性绑定到新的xib视图,则将面临此问题。

  • YourMainViewController-管理-mainView
  • CustomView-需要在需要时从xib加载。

如果您在内部加载了以下代码,则下面的代码将在以后引起混乱YourMainViewController。这是因为self.view从现在开始将引用您的customview

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

好的,因此,基本上,最佳实践指出每个UIView应该都有一个关联的控制器吗?
亚当·怀特

不必要。但是,当从单独的xib加载视图时,这将是没有问题的方法。IOS5之前的苹果医生说,不应使用一个视图控制器来控制多个场景(xib),反之亦然。
拉克什

围栏实际上只是警报视图的大小
Adam Waite

我个人认为,是否必须创建新的控制器取决于代码/ xib的复杂性。如果您能够处理成功导致的问题,并且不打算将来进行修改,那也不是问题。但是创建一个单独的视图控制器将为您解决很多问题。并且由于您的情况只是一个活动指示器,因此您可以轻松使用UIViewController(如答案中所述)对象。我将更新答案以相应地执行此操作。
Rakesh 2013年

1
因此,我可以在界面生成器中绘制界面,而无需通过编程来完成。以编程方式完成它不是问题,我只想知道如何继承UIView并给它一个笔尖!这应该不难。
亚当·怀特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.