iPhone:创建一个可重用的组件(控件),它包含一些Interface Builder部件和一些代码

时间:2010-10-27 18:22:20

标签: iphone xcode interface-builder

我想为iPhone创建一个可重用的组件(自定义控件)。它包含在View上预先安排的几个标准控件,然后是一些相关的代码。我的目标是:

  1. 我希望能够使用Interface Builder在我的自定义控件中布置子视图;
  2. 我想以某种方式打包整个事情,以便我可以相当容易地将生成的自定义组件拖放到其他视图中,而无需手动重新连接一堆插座等等。 (手动重新布线很好,我只是不想做吨和吨。)
  3. 让我更具体一点,并具体告诉你我的控制应该做什么。在我的应用程序中,我有时需要点击Web服务来验证用户输入的数据。在等待来自Web服务的回复时,我想显示一个微调器(一个活动指示器)。如果Web服务使用成功代码回复,我想显示“成功”复选标记。如果Web服务回复错误代码,我想显示错误图标和错误消息。

    执行此操作的一次性方法非常简单:我只创建一个包含UIActivityIndi​​catorView,两个UIImages(一个用于成功图标,一个用于错误图标)的UIView,以及一个用于错误消息的UILabel。这是一个截图,相关部分标有红色:

    alt text

    然后我将这些部分连接到插座,然后在控制器中放入一些代码。

    但是如何打包这些部分 - 代码和一小部分视图 - 以便我可以重用它们?以下是我发现的一些让我在那里的事情,但不是很好:

    • 我可以将视图和控件的集合拖到库的自定义对象部分;然后,我可以将它们拖回到其他视图中。但是(a)它忘记了哪些图像与两个UIImages相关联,(b)有很多手动重新布线的四个或五个插座,(c)最重要的是,这并没有带来代码。 (也许有一种简单的方法可以连接代码?)
    • 我想我可以创建一个IBPlugin;不确定这是否会有所帮助,而且看起来很多工作,而且我不完全清楚IBPlugins是否适用于iPhone开发。
    • 我想,“嗯,有与之相关的代码 - 闻起来就像一个控制器”,所以我尝试用相关的XIB文件创建一个自定义控制器(例如WebServiceValidatorController)。这实际上感觉很有希望,但在那时我无法弄清楚在Interface Builder中如何将此组件拖到其他视图上。 WebServiceValidatorController是控制器,而不是视图,因此我可以将其拖到文档窗口中,但不能拖到视图中。

    我有一种感觉,我错过了一些明显的东西......

2 个答案:

答案 0 :(得分:21)

以下是我解决类似问题的方法:我想:

  • 创建可重用,自包含的“小部件模块类”,实现从多个UIKit组件(以及其他嵌套的小部件模块类!)构建的复杂视图。这些小部件类的高级客户类不关心什么是UILabel,小部件内部是什么UIImageView,客户类只关心“显示分数”或“显示团队徽标”等概念。

  • 在小部件模块类中
  • ,使用界面构建器为小部件和连接插座布置UI

  • 对于窗口小部件的更高级别客户,我希望能够将窗口小部件的框架放置在界面构建器中的客户视图中,而无需为IB等设计自定义插件。

这些小部件不是顶级控制器:因此将它们作为UIViewController的子类是没有意义的。然后还有Apple的建议,一次不要在可见屏幕上有多个VC。此外,还有很多建议和意见,如“MVC!MVC!你必须将你的视图与你的控制分开!MVC!”人们非常不鼓励从子类化UIView或者将应用程序逻辑放在UIView子类中。

但无论如何我决定这样做(子类UIView)。我已经在这块了几次(但对于iPhone SDK / UIKit仍然相当新),而且我对设计非常敏感,这可能会导致问题,我坦率地看不到问题使用子类化UIView并添加出口和操作。事实上,使可重用小部件成为UIView的子类而不是基于UIViewController有很多优点:

  • 您可以在客户视图的界面构建器布局中放置窗口小部件类的直接实例,并且在从xib加载客户类时,将正确初始化窗口小部件视图的UIView框架。这比我在客户中放置“占位符视图”,然后以编程方式实例化窗口小部件模块,并将窗口小部件的框架设置为占位符的框架,然后交换窗口小部件视图的占位符视图更加清晰。

  • 您可以创建一个干净简单的xib文件,以在界面构建器中布置窗口小部件的组件。文件所有者设置为您的窗口小部件类。 nib文件包含一个根(vanilla)UIView,其中布局了所有GUI,然后在窗口小部件的awakeFromNib:方法中,此nib视图作为子视图添加到窗口小部件本身。任何帧大小调整都可以在这里处理,完全在小部件代码中,如果有必要的话。像这样:

- (void) awakeFromNib
{
    [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil];
    [self addSubview:self.view];
}
  • 小部件通过在其自己的awakeFromNib方法中使用NSBundle的loadNibNamed:owner:options方法自行初始化其用户界面,因此将小部件集成到客户类中是干净的:只需将UIView放入IB中的客户视图,更改UIView的类到MyWidgetView,在客户的init或viewDidLoad中使用您自己编写的小部件API在小部件的显示中设置任何默认值(setScore:setTeamImage:etc。)

在我看来,在以这种方式子类化UIView以生成可重用的“小部件”和使用UIViewController之间,结果代码基本上没有区别。在这两种情况下,.h文件都包含窗口小部件类成员,出口,操作声明和特殊API声明。在这两种情况下,.m文件都包含动作实现和特殊的API实现。并且视图与控件分离 - 视图位于xib文件中!

因此,总而言之,这就是我为打包复杂的可重用视图小部件所做的工作:

  • 使widget类成为UIView的子类
  • 通过从工具库中拖动UIView并将类更改为“MyWidgetClass”,直接在IB中的应用程序的其他视图中实例化小部件。
  • 在根香草UIView内的笔尖中设计您的小部件UI。
  • 在widget类的awakeFromNib:方法中的
  • ,从xib加载小部件的UI并执行 [self addSubview:self.theXibRootView]

  • 像对待UIViewController一样对小部件进行编码:出口,操作,特殊API

我有兴趣了解用于创建可重用小部件的其他结构系统,以及这种方法的优势。

如果窗口小部件需要向客户类报告事件,只需使用委托协议,客户将窗口小部件的委托设置为self,就像在UIViewController中一样。

答案 1 :(得分:12)

我创建了类似的构造,除了我不在IB中使用结果,而是使用代码实例化。我将描述它是如何工作的,最后我会告诉你如何用它来完成你所追求的目标。

我从一个空的XIB文件开始,在顶层添加一个自定义视图。我将自定义视图配置为我的类。在视图层次结构中,我根据需要创建和配置子视图。

我在自定义视图类中创建了所有IBOutlet,并将它们连接到那里。在本练习中,我完全忽略了“文件所有者”。

现在,当我需要创建视图时(通常在控制器中作为while / for循环的一部分,根据需要创建尽可能多的部分),我使用NSBundle的功能如下:

- (void)viewDidLoad
{
    CGRect fooBarViewFrame = CGRectMake(0, 0, self.view.bounds.size.width, FOOBARVIEW_HEIGHT);
    for (MBSomeData *data in self.dataArray) {
        FooBarView *fooBarView = [self loadFooBarViewForData:data];
        fooBarView.frame = fooBarViewFrame;
        [self.view addSubview:fooBarView];

        fooBarViewFrame = CGRectOffset(fooBarViewFrame, 0, FOOBARVIEW_HEIGHT);
    }
}

- (FooBarView*)loadFooBarViewForData:(MBSomeData*)data
{
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"FooBarView" owner:self options:nil];
    FooBarView *fooBarView = [topLevelObjects objectAtIndex:0];
    fooBarView.barView.amountInEuro = data.amountInEuro;
    fooBarView.barView.gradientStartColor = data.color1;
    fooBarView.barView.gradientMidColor = data.color2;
    fooBarView.titleLabel.text = data.name;
    fooBarView.titleLabel.textColor = data.nameColor;
    return fooBarView;
}

注意,我如何将nib的所有者设置为self - 因为我没有将“文件所有者”连接到任何东西,所以没有任何危害。加载这个笔尖唯一有价值的结果是它的第一个元素 - 我的观点。

如果你想在IB中创建视图来调整这种方法,那很简单。在主自定义视图中实现子视图的加载。将子视图的帧设置为主视图的边界,以便它们具有相同的大小。主视图将成为您的真实自定义视图的容器,也是外部代码的接口 - 您只打开其子视图所需的属性,其余部分被封装。然后,您只需在IB中删除自定义视图,将其配置为您的类,并照常使用它。