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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4036398/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 19:53:01  来源:igfitidea点击:

iPhone: Create a reusable component (control) that has some Interface Builder pieces and some code

iphonexcodeinterface-builder

提问by Mike Morearty

I want to create a reusable component (a custom control) for the iPhone. It consists of several standard controls prearranged on a View, and then some associated code. My goals are:

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

  1. I want to be able to use Interface Builder to lay out the subviews in my custom control;
  2. I want to somehow package the whole thing up so that I can then fairly easily drag and drop the resulting custom component into other Views, without having to manually rewire a bunch of outlets and so on. (A little manual rewiring is fine, I just don't want to do tons and tons of it.)
  1. 我希望能够使用 Interface Builder 在我的自定义控件中布置子视图;
  2. 我想以某种方式打包整个东西,这样我就可以很容易地将生成的自定义组件拖放到其他视图中,而无需手动重新连接一堆插座等等。(稍微手动重新布线很好,我只是不想做大量的事情。)

Let me be more concrete, and tell you specifically what my control is supposed to do. In my app, I sometimes need to hit a web service to validate data that the user has entered. While waiting for a reply from the web service, I want to display a spinner (an activity indicator). If the web services replies with a success code, I want to display a "success" checkmark. If the web service replies with an error code, I want to display an error icon and an error message.

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

The single-use way to do this is pretty easy: I just create a UIView that contains a UIActivityIndicatorView, two UIImages (one for the success icon and one for the error icon), and a UILabel for the error message. Here's a screenshot, with the relevant parts marked in red:

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

alt text

替代文字

I then wire up the pieces to outlets, and I put some code in my controller.

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

But how do I package up those pieces -- the code and the little collection of views -- so that I can reuse them? Here are a few things I found that get me partway there, but aren't that great:

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

  • I can drag the collection of views and controls into the Custom Objects section of the Library; then, later, I can drag them back out onto other views. But (a) it forgets which images were associated with the two UIImages, (b) there is a lot of manual rewiring of four or five outlets, and (c) most importantly, this doesn't do bring along the code. (Perhaps there's an easy way to wire up the code?)
  • I think I could create an IBPlugin; not sure if that would help, and it seems like a lot of work, and also it's not entirely clear to me whether IBPlugins work for iPhone development.
  • I thought, "Hmm, there's code associated with this -- that smells like a controller," so I tried creating a custom controller (e.g. WebServiceValidatorController) with associated XIB file. That actually feels really promising, but then at that point I can't figure out how, in Interface Builder, to drag this component onto other views. The WebServiceValidatorControlleris a controller, not a view, so I can drag it into a Document Window, but not into a view.
  • 我可以将视图和控件的集合拖到库的自定义对象部分;然后,稍后,我可以将它们拖回其他视图。但是 (a) 它忘记了哪些图像与两个 UIImages 相关联,(b) 需要手动重新连接四五个插座,并且 (c) 最重要的是,这并没有带来代码。(也许有一种简单的方法来连接代码?)
  • 我想我可以创建一个 IBPlugin;不确定这是否会有所帮助,而且似乎需要做很多工作,而且我也不完全清楚 IBPlugins 是否适用于 iPhone 开发。
  • 我想,“嗯,有与此相关的代码 - 闻起来像一个控制器”,所以我尝试创建一个WebServiceValidatorController带有关联 XIB 文件的自定义控制器(例如)。这实际上感觉很有希望,但那时我无法弄清楚如何在 Interface Builder 中将此组件拖到其他视图上。这WebServiceValidatorController是一个控制器,而不是一个视图,所以我可以将它拖到一个文档窗口中,但不能拖到一个视图中。

I have a feeling I'm missing something obvious...

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

采纳答案by Michal

I've created similar constructs except that I do not use the result in IB, but instantiate using the code. I'll describe how that works, and at the end I'll give you a hint how that can be used to accomplish what you're after.

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

I start from an empty XIB file where I add one custom view at the top level. I configure that custom view to be my class. Below in view hierarchy I create and configure subviews as required.

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

I create all IBOutlets in my custom-view class, and connect them there. In this exercise I ignore the "File's owner" completely.

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

Now, when I need to create the view (usually in controller as part of while/for-loop to create as much of them as needed), I use NSBundle's functionality like this:

现在,当我需要创建视图时(通常在控制器中作为 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;
}

Notice, how I set owner of nib to self- there's no harm as I didn't connect "File's owner" to anything. The only valuable result from loading this nib is its first element - my view.

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

If you want to adapt this approach for creating views in IB, it's pretty easy. Implement loading of subview in a main custom view. Set the subview's frame to main view's bounds, so that they are the same size. The main view will become the container for your real custom view, and also an interface for external code - you open only needed properties of its subviews, the rest is encapsulated. Then you just drop custom view in IB, configure it to be your class, and use it as usual.

如果您想采用这种方法在 IB 中创建视图,这很容易。在主自定义视图中实现子视图的加载。将子视图的框架设置为主视图的边界,使它们的大小相同。主视图将成为您真正的自定义视图的容器,也是外部代码的接口——您只打开其子视图所需的属性,其余的则被封装。然后您只需在 IB 中删除自定义视图,将其配置为您的类,然后照常使用它。

回答by Bogatyr

Here is how I'm solving a similar problem: I wanted to:

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

  • create reusable, self-contained "widget module classes" implementing a complex view built from multiple UIKit components (and other nested widget module classes!). The higher-level customer classes of these widget classes don't care about what's a UILabel, what's a UIImageView internally within the widget, the customer classes only care about concepts like "displaying the score" or "showing the team logo."

  • within a widget module class, lay out the UI for the widget and hookup outlets using interface builder

  • for higher level customers of a widget, I wanted to be able to place the frame of the widget within the view of the customer in interface builder without having to design custom plugins to IB, etc.

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

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

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

These widgets are not top level controllers: so it makes no sense for them to be subclasses of UIViewController. Then also there's the Apple advice not to have more than one VC on a visible screen at a time. Also, there's so much advice and opinion floating around like "MVC! MVC! You must separate your View and your control! MVC!" that people are so strongly discouraged from subclassing UIView or ever placing app logic within a UIView subclass.

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

But I decided to do it anyway (subclass UIView). I've been around the block a few times (but fairly new still to the iPhone SDK/UIKit), and I'm very sensitive to design that is ugly, and that can cause problems, and I frankly don't see the problem with subclassing UIView and adding outlets and actions. In fact there are many advantages to making your reusable widget be a subclass of UIView rather than be UIViewController-based:

但我还是决定这样做(子类 UIView)。我已经来过这个街区几次(但对 iPhone SDK/UIKit 来说还是相当新的),我对丑陋的设计非常敏感,这可能会导致问题,坦率地说,我没有看到问题继承 UIView 并添加插座和动作。事实上,让你的可重用小部件成为 UIView 的子类而不是基于 UIViewController 有很多优点:

  • You can place a direct instance of the widget class in a customer view's interface builder layout, and the widget view's UIView frame will be properly initialized when the customer class is loaded from the xib. This is much cleaner to me than putting a "placeholder view" in the customer, then instantiating the widget module programmatically, and setting the widget's frame to that of the placeholder, and then swapping the placeholder view for the widget's view.

  • You can create a clean and simple xib file to lay out the widget's components in interface builder. The File's Owner is set to your widget class. The nib file contains a root (vanilla) UIView where all of the GUI is laid out, and then in the awakeFromNib: method of the widget, this nib view is added to the widget itself as a subview. Any frame size adjustments can be handled here, entirely within the widget code, if any is necessary. Like so:

  • 您可以在客户视图的界面构建器布局中放置小部件类的直接实例,当从 xib 加载客户类时,小部件视图的 UIView 框架将被正确初始化。这对我来说比在客户中放置一个“占位符视图”,然后以编程方式实例化小部件模块,并将小部件的框架设置为占位符的框架,然后将占位符视图交换为小部件的视图要干净得多。

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

- (void) awakeFromNib
{
    [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil];
    [self addSubview:self.view];
}
  • The widget self-initialzes its user interface by using NSBundle's loadNibNamed:owner:options method in its own awakeFromNib method, so integration of the widget into customer classes is clean: just drop a UIView into the customer's view in IB, change the UIView's class to MyWidgetView, and in the customer's init or viewDidLoad set up any defaults in the widget's display using the widget's API that you write yourself (setScore: setTeamImage: etc.)
  • 小部件通过在其自己的awakeFromNib 方法中使用NSBundle 的loadNibNamed:owner:options 方法自初始化其用户界面,因此将小部件集成到客户类中是干净的:只需将UIView 放入IB 中的客户视图中,将UIView 的类更改为MyWidgetView,并在客户的 init 或 viewDidLoad 中使用您自己编写的小部件 API 设置小部件显示中的任何默认值(setScore: setTeamImage: 等)

It seems to me there is essentially no difference in the resulting code between subclassing a UIView in this way to make a reusable "widget", and using a UIViewController. In both cases the .h files contain widget class members, outlets, action declarations, and special API declarations. In both cases the .m file contains action implementations, and special API implementation. And the view IS separated from the control -- the view is in the xib file!

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

So, in summary, this is what I do for packaging up complex reusable view widgets:

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

  • Make the widget class a subclass of UIView
  • Instantiate the widget wherever you want, in other views in your app in IB directly by dragging a UIView from the tools library, and changing the class to your "MyWidgetClass."
  • Design your widget's UI in IB in a nib inside a root vanilla UIView.
  • in the widget class's awakeFromNib: method, load the widget's UI from xib and do [self addSubview:self.theXibRootView]

  • Code the widget just like you would a UIViewController: outlets, actions, special APIs

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

  • 像编写 UIViewController 一样编写小部件:插座、动作、特殊 API

I'd be interested in hearing about other structural systems for creating reusable widgets, and what the advantages are over this approach.

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

If the widget needs to report events to the customer class, just use a delegate protocol where the customer sets the widget's delegate to self, just like in a UIViewController.

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