【问题标题】:Correct way to load a Nib for a UIView subclass为 UIView 子类加载 Nib 的正确方法
【发布时间】:2013-03-02 14:29:39
【问题描述】:

我知道以前有人问过这个问题,但答案相互矛盾,我很困惑,所以请不要喷我。

我希望在我的应用程序中拥有一个可重用的 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;
    }

但我正在覆盖 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) {
                        }];

仍然需要正确的做法

是否应该使用视图控制器和带有 nib 名称的初始化来完成?我应该在代码中的一些 UIView 初始化方法中设置笔尖吗?还是我做的没问题?

【问题讨论】:

  • 但是第一行代码不是和你在initWithDataSource的原始问题中使用的几乎一样吗?无论如何,即使您将所有者指定为“Nil”,这也将起作用。
  • 值得注意的是,如今在 99.99999% 的情况下,人们只会使用 容器视图,它们现在无处不在,并且始终在 iOS 中使用。 how-to article
  • 旁注,您可以将 [NSString stringWithFormat:@"%@",[FSASelectView class]] 替换为 NSStringFromClass([FSASelectView class])

标签: iphone ios objective-c uiview xib


【解决方案1】:
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 中,您会单击文件所有者并将文件所有者设置为YourClass。然后实际上 在 YourClass.h 你会有 IBOutlet 属性。在TopArea.xib 中,您可以将控件拖动到这些插座。

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

【讨论】:

  • 没有用于 UIView 的 initWithNibName: 方法,仅用于 UIViewController
  • 那是一个视图控制器,我想使用一个 UIView 并让创建它的控制器拥有它。
  • 对不起,我匆忙复制了错误的代码,我已经更新了答案,请看一下。
  • 我将把它标记为正确。我不知道这是否是最佳做法,但它有效。
  • @Joe Blow 对不起,为什么要这样做:“不要忘记在 TopArea.xib 中,您可能必须单击视图本身并将其拖到某个出口,因此如有必要,您可以控制它。”您可以改为使用 myViewObject 来访问底层视图,因为它本身就是 UIView 吗?为什么需要房产?
【解决方案2】:

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

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

CustomView 类的.m 文件中,重写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];

【讨论】:

  • 从 iOS 9 开始,这是此页面上列出的唯一对我有效的解决方案(其他解决方案会导致崩溃)。
  • 请注意,此方法与从现有 XIB 加载自定义视图不兼容,因为它不调用 -init。相反,它将调用initWithFrame:-awakeFromNib。如果您编写与 -init 方法中相同的代码来加载视图,您将进入无限循环。
【解决方案3】:

按照以下步骤操作

  1. 创建一个名为 MyView .h/.m 的类,类型为 UIView
  2. 创建一个同名的 xib MyView.xib
  3. 现在将文件所有者类从 xib 中的 NSObject 更改为 UIViewController。见下图
  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,UIViewController 拥有的 View 实际上是您在 MyView xib 中分配的MyView

演示 我做了一个demo抓here

【讨论】:

  • 为什么人们拒绝投票,错了?我还没有尝试过,但这看起来不会做我想要的。
  • 我投了反对票,但那是因为我误读了您的答案(以为您将 UIView 子类分配为文件的所有者)。对此感到抱歉。
  • 在您的 xib 文件中忽略文件所有者。然后,您可以避免创建 UIViewController 只需执行 MyView *view = [[UINib instantiateWithOwner:nil options:nil] lastObject];
  • @LightningStryk 当然可以,但这只是另一种方式。
  • 这是创建一个可重用的 UIView 子类,而不是为此目的使用 UIViewController。
【解决方案4】:

在这里回答我自己关于 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()

【讨论】:

    【解决方案5】:

    在斯威夫特中:

    例如,您的自定义类的名称是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 Owner 设置为UIViewController

    将您的 View 重命名为 InfoView

    右键单击File's Owner 并将您的view 字段与您的InfoView 连接起来:

    确保类名是InfoView:

    在此之后,您可以毫无问题地将操作添加到自定义类中的按钮:

    在你的MainViewController 中使用这个自定义类:

    func someMethod() {
        var v = InfoView.instanceFromNib()
        v.frame = self.view.bounds
        self.view.addSubview(v)
    }
    

    【讨论】:

      【解决方案6】:

      您可以使用视图控制器初始化 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 时将另一个视图控制器(已用于另一个视图)作为所有者传递并不是一个好主意,因为控制器的视图属性将被更改,并且当您想要访问原始视图时,您不会引用它。

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

      即;

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

      下面的代码稍后会引起混淆,如果你在视图中编写它确实加载了YourMainViewController。那是因为self.view 从此时起将引用您的自定义视图

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

      【讨论】:

      • 好的,基本上,最佳实践表明每个 UIView 都应该有一个关联的控制器?
      • 不一定。但是当从单独的 xib 加载视图时,这将是没有问题的方式。 IOS5 之前的苹果文档说,一个视图控制器不应该用于控制多个场景 (xib),反之亦然。
      • xib 实际上只有警报视图的大小
      • 个人感觉,要不要新建控制器,要看code/xib的复杂程度。如果您可以成功处理导致的问题,并且如果您不考虑将来的修改,那不是问题。但是创建一个单独的视图控制器会为你解决很多问题。而且由于在您的情况下它只是一个活动指示器,您可以轻松地使用 UIViewController(如答案中所述)对象。我会相应地更新我的答案。
      • 所以我可以在界面生成器中绘制界面,而不是以编程方式进行。以编程方式执行它不是问题,我只想知道如何子类化 UIView 并给它一个笔尖!这应该不难。
      猜你喜欢
      • 1970-01-01
      • 2013-04-29
      • 2011-03-22
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 2011-02-08
      • 1970-01-01
      相关资源
      最近更新 更多