我想为iPhone创建一个可重用的组件(自定义控件)。它包含在View上预先安排的几个标准控件,然后是一些相关的代码。我的目标是:
让我更具体一点,并具体告诉你我的控制应该做什么。在我的应用程序中,我有时需要点击Web服务来验证用户输入的数据。在等待来自Web服务的回复时,我想显示一个微调器(一个活动指示器)。如果Web服务使用成功代码回复,我想显示“成功”复选标记。如果Web服务回复错误代码,我想显示错误图标和错误消息。
执行此操作的一次性方法非常简单:我只创建一个包含UIActivityIndicatorView,两个UIImages(一个用于成功图标,一个用于错误图标)的UIView,以及一个用于错误消息的UILabel。这是一个截图,相关部分标有红色:
然后我将这些部分连接到插座,然后在控制器中放入一些代码。
但是如何打包这些部分 - 代码和一小部分视图 - 以便我可以重用它们?以下是我发现的一些让我在那里的事情,但不是很好:
WebServiceValidatorController
)。这实际上感觉很有希望,但在那时我无法弄清楚在Interface Builder中如何将此组件拖到其他视图上。 WebServiceValidatorController
是控制器,而不是视图,因此我可以将其拖到文档窗口中,但不能拖到视图中。我有一种感觉,我错过了一些明显的东西......
答案 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];
}
在我看来,在以这种方式子类化UIView以生成可重用的“小部件”和使用UIViewController之间,结果代码基本上没有区别。在这两种情况下,.h文件都包含窗口小部件类成员,出口,操作声明和特殊API声明。在这两种情况下,.m文件都包含动作实现和特殊的API实现。并且视图与控件分离 - 视图位于xib文件中!
因此,总而言之,这就是我为打包复杂的可重用视图小部件所做的工作:
,从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中删除自定义视图,将其配置为您的类,并照常使用它。