【问题标题】:iPhone: Create a reusable component (control) that has some Interface Builder pieces and some codeiPhone:创建一个包含一些 Interface Builder 片段和一些代码的可重用组件(控件)
【发布时间】:2011-05-01 11:40:38
【问题描述】:

我想为 iPhone 创建一个可重用的组件(自定义控件)。它由预先安排在视图上的几个标准控件和一些相关代码组成。我的目标是:

  1. 我希望能够使用 Interface Builder 在我的自定义控件中布置子视图;
  2. 我想以某种方式将整个东西打包,这样我就可以相当轻松地将生成的自定义组件拖放到其他视图中,而无需手动重新连接一堆插座等等。 (一点点手动重新布线就可以了,我只是不想做大量的工作。)

让我更具体一些,具体告诉你我的控件应该做什么。在我的应用程序中,有时我需要访问 Web 服务来验证用户输入的数据。在等待来自 Web 服务的回复时,我想显示一个微调器(一个活动指示器)。如果 Web 服务回复成功代码,我想显示“成功”复选标记。如果 Web 服务回复错误代码,我想显示错误图标和错误消息。

实现这一点的一次性方法非常简单:我只需创建一个 UIView,其中包含一个 UIActivityIndi​​catorView、两个 UIImage(一个用于成功图标,一个用于错误图标)和一个用于错误消息的 UILabel。这是一个屏幕截图,相关部分用红色标记:

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

但是我如何打包这些部分——代码和少量视图集合——以便我可以重用它们?以下是我发现的一些事情,让我成功了,但不是那么好:

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

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

【问题讨论】:

标签: iphone xcode interface-builder


【解决方案1】:

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

  • 创建可重用、自包含的“小部件模块类”,实现从多个 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:等)

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

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

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

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

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

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

【讨论】:

  • 谢谢。这正是我想要的。
【解决方案2】:

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

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

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

现在,当我需要创建视图时(通常在控制器中作为 while/for-loop 的一部分以根据需要创建尽可能多的视图),我使用 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 中,将其配置为你的类,然后像往常一样使用它。

【讨论】:

  • 您不应该依赖从 loadNibNamed:owner:options: 返回的对象的顺序,相反,您应该检查返回的对象的类/标签值。正如我在回答中概述的那样,使用 File's Owner 和 outlets 的一个优点是您不需要遍历从此方法返回的对象。
  • 好吧,我使用的事实是,我只向 XIB 添加了一个顶级对象 - 因为只有一个,所以不迭代而是拾取第一个是非常安全的。
  • 很好的功能解决方案,我仍然希望有一些方法可以重用 IB 中的预布局对象
  • 伙计们是 FooBarView 继承自 UIView 或 UIViewController,我对此很陌生?
  • 它继承自 UIView :) 抱歉回复晚了 :)
猜你喜欢
  • 2021-01-01
  • 1970-01-01
  • 2020-07-19
  • 2019-09-23
  • 2023-01-24
  • 2011-08-12
  • 2013-04-28
  • 2018-12-16
  • 1970-01-01
相关资源
最近更新 更多