如何创建自定义 iOS 视图类并实例化它的多个副本(在 IB 中)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9251202/
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-08-30 16:47:52  来源:igfitidea点击:

How do I create a custom iOS view class and instantiate multiple copies of it (in IB)?

iosxcodeclassuiviewuiviewcontroller

提问by Michael Campsall

I am currently making an app that will have multiple timers, which are basically all the same.

我目前正在制作一个具有多个计时器的应用程序,它们基本上都是一样的。

I want to create a custom class that uses all of the code for the timers as well as the layout/animations, so I can have 5 identical timers that operate independently of each other.

我想创建一个自定义类,该类使用定时器的所有代码以及布局/动画,因此我可以拥有 5 个彼此独立运行的相同定时器。

I created the layout using IB (xcode 4.2) and all the code for the timers is currently just in the viewcontroller class.

我使用 IB (xcode 4.2) 创建了布局,计时器的所有代码目前都在 viewcontroller 类中。

I am having difficulty wrapping my brain around how to encapsulate everything into a custom class and then add it to the viewcontroller, any help would be much appreciated.

我很难围绕如何将所有内容封装到自定义类中,然后将其添加到视图控制器中,任何帮助将不胜感激。

回答by NJones

Well to answer conceptually, your timer should likely be a subclass of UIViewinstead of NSObject.

好吧,从概念上回答,您的计时器应该是UIView而不是 的子类NSObject

To instantiate an instance of your timer in IB simply drag out a UIViewdrop it on your view controller's view, and set it's class to your timer's class name.

要在 IB 中实例化计时器的实例,只需将其UIView拖放到视图控制器的视图上,然后将其类设置为计时器的类名。

enter image description here

在此处输入图片说明

Remember to #importyour timer class in your view controller.

请记住#import您的视图控制器中的计时器类。

Edit: for IB design (for code instantiation see revision history)

编辑:对于 IB 设计(代码实例见修订历史)

I'm not very familiar at all with storyboard, but I do know that you can construct your interface in IB using a .xibfile which is nearly identical to using the storyboard version; You should even be able to copy & paste your views as a whole from your existing interface to the .xibfile.

我对情节提要不是很熟悉,但我知道您可以.xib使用与情节提要版本几乎相同的文件在 IB 中构建您的界面;您甚至应该能够将您的视图作为一个整体从现有界面复制并粘贴到.xib文件中。

To test this out I created a new empty .xibnamed "MyCustomTimerView.xib". Then I added a view, and to that added a label and two buttons. Like So:

为了测试这一点,我创建了一个.xib名为“MyCustomTimerView.xib”的新空。然后我添加了一个视图,并添加了一个标签和两个按钮。像这样:

enter image description here

在此处输入图片说明

I created a new objective-C class subclassing UIViewnamed "MyCustomTimer". In my .xibI set my File's Ownerclass to be MyCustomTimer. Now I'm free to connect actions and outlets just like any other view/controller. The resulting .hfile looks like this:

我创建了一个UIView名为“MyCustomTimer”的新的 Objective-C 类子类。在我的文件中,.xib我将File's Owner类设置为MyCustomTimer。现在我可以像任何其他视图/控制器一样自由地连接动作和出口。生成的.h文件如下所示:

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

The only hurdle left to jump is getting this .xibon my UIViewsubclass. Using a .xibdramatically cuts down the setup required. And since you're using storyboards to load the timers we know -(id)initWithCoder:is the only initializer that will be called. So here is what the implementation file looks like:

剩下的唯一障碍是.xib在我的UIView子类中获得它。使用.xib显着减少所需的设置。由于您使用故事板来加载计时器,我们知道这-(id)initWithCoder:是唯一会被调用的初始化程序。所以这里是实现文件的样子:

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self addSubview:
         [[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" 
                                        owner:self 
                                      options:nil] objectAtIndex:0]];
    }
    return self;
}
- (IBAction)startButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

The method named loadNibNamed:owner:options:does exactly what it sounds like it does. It loads the Nib and sets the "File's Owner" property to self. We extract the first object in the array and that is the root view of the Nib. We add the view as a subview and Voila it's on screen.

命名的方法loadNibNamed:owner:options:完全符合它的要求。它加载 Nib 并将“文件的所有者”属性设置为 self。我们提取数组中的第一个对象,即 Nib 的根视图。我们将视图添加为子视图,瞧它在屏幕上。

Obviously this just changes the label's background color when the buttons are pushed, but this example should get you well on your way.

显然,这只是在按下按钮时更改标签的背景颜色,但是这个示例应该可以让您顺利进行。

Notes based on comments:

基于评论的注释:

It is worth noting that if you are getting infinite recursion problems you probably missed the subtle trick of this solution. It's not doing what you think it's doing. The view that is put in the storyboard is not seen, but instead loads another view as a subview. That view it loads is the view which is defined in the nib. The "file's owner" in the nib is that unseen view. The cool part is that this unseen view is still an Objective-C class which may be used as a view controller of sorts for the view which it brings in from the nib. For example the IBActionmethods in the MyCustomTimerclass are something you would expect more in a view controller than in a view.

值得注意的是,如果您遇到无限递归问题,您可能错过了这个解决方案的微妙技巧。它没有做你认为它在做的事情。放置在故事板中的视图不会被看到,而是加载另一个视图作为子视图。它加载的视图是在笔尖中定义的视图。笔尖中的“文件所有者”是那个看不见的视图。很酷的部分是这个看不见的视图仍然是一个 Objective-C 类,它可以用作它从笔尖引入的视图的各种视图控制器。例如,类中的IBAction方法在MyCustomTimer视图控制器中比在视图中更符合您的期望。

As a side note, some may argue that this breaks MVCand I agree somewhat. From my point of view it's more closely related to a custom UITableViewCell, which also sometimes has to be part controller.

作为旁注,有些人可能会争辩说这打破了MVC,我有点同意。在我看来,它与 custom 更密切相关UITableViewCell,它有时也必须是部分控制器。

It is also worth noting that this answer was to provide a very specific solution; create one nib that can be instantiated multiple times on the same viewas laid out on a storyboard. For example, you could easily imagine six of these timers all on an iPad screen at one time. If you only need to specify a view for a view controller that is to be used multiple times across your application then the solution provided by jyavenard to this questionis almost certainly a better solution for you.

还值得注意的是,这个答案是提供一个非常具体的解决方案;创建一个可以在情节提要上布局的同一视图上多次实例化的笔尖。例如,您可以轻松想象一次在 iPad 屏幕上显示六个这样的计时器。如果您只需要为要在整个应用程序中多次使用的视图控制器指定一个视图,那么jyavenard 为这个问题提供的解决方案几乎肯定对您来说是一个更好的解决方案。

回答by Suragch

Swift example

Swift 示例

Updated for Xcode 10 and Swift 4

为 Xcode 10 和 Swift 4 更新

Here is a basic walk through. I originally learned a lot of this from watching this Youtube video series. Later I updated my answer based on this article.

这是一个基本的演练。我最初从观看这个 Youtube 视频系列中学到了很多。后来我根据这篇文章更新了我的答案。

Add custom view files

添加自定义视图文件

The following two files will form your custom view:

以下两个文件将构成您的自定义视图:

  • .xib file to contain the layout
  • .swift file as UIViewsubclass
  • .xib 文件以包含布局
  • .swift 文件作为UIView子类

The details for adding them are below.

添加它们的详细信息如下。

Xib file

Xib文件

Add a .xib file to your project (File > New > File... > User Interface > View). I am calling mine ReusableCustomView.xib.

将 .xib 文件添加到您的项目(文件 > 新建 > 文件... > 用户界面 > 查看)。我打电话给我的ReusableCustomView.xib

Create the layout that you want your custom view to have. As an example, I will make a layout with a UILabeland a UIButton. It is a good idea to use auto layout so that things will resize automatically no matter what size you set it to later. (I used Freeformfor the xib size in the Attributes inspector so that I could adjust the simulated metrics, but this isn't necessary.)

创建您希望自定义视图具有的布局。例如,我将使用 aUILabel和 a进行布局UIButton。使用自动布局是个好主意,这样无论您稍后将其设置为什么大小,内容都会自动调整大小。(我在属性检查器中使用Freeform作为 xib 大小,以便我可以调整模拟指标,但这不是必需的。)

enter image description here

在此处输入图片说明

Swift file

斯威夫特文件

Add a .swift file to your project (File > New > File... > Source > Swift File). It is a subclass of UIViewand I am calling mine ReusableCustomView.swift.

将 .swift 文件添加到您的项目(文件 > 新建 > 文件... > 源 > Swift 文件)。它是 的一个子类,UIView我称之为我的ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Make the Swift file the owner

将 Swift 文件设为所有者

Go back to your .xib fileand click on "File's Owner" in the Document Outline. In the Identity Inspector write the name of your .swift fileas the custom class name.

返回到您的.xib 文件并单击文档大纲中的“文件所有者”。在 Identity Inspector 中,将.swift 文件的名称写入自定义类名称。

enter image description here

在此处输入图片说明

Add Custom View Code

添加自定义视图代码

Replace the ReusableCustomView.swiftfile's contents with the following code:

ReusableCustomView.swift文件内容替换为以下代码:

import UIKit

@IBDesignable
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView:UIView?

    @IBOutlet weak var label: UILabel!

    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    func commonInit() {
        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Be sure to get the spelling right for the name of your .xib file.

确保 .xib 文件的名称拼写正确。

Hook up the Outlets and Actions

连接插座和动作

Hook up the outlets and actions by control dragging from the label and button in the xib layout to the swift custom view code.

通过控制从 xib 布局中的标签和按钮拖动到 swift 自定义视图代码来连接出口和动作。

Use you custom view

使用您的自定义视图

Your custom view is finished now. All you have to do is add a UIViewwherever you want it in your main storyboard. Set the class name of the view to ReusableCustomViewin the Identity Inspector.

您的自定义视图现已完成。您所要做的就是UIView在主故事板中的任何位置添加一个。ReusableCustomView在 Identity Inspector 中设置视图的类名。

enter image description here

在此处输入图片说明

回答by jyavenard

Answer for view controllers, not views:

视图控制器的答案,而不是视图

There is an easier way to load a xib from a storyboard. Say your controller is of MyClassController type which inherit from UIViewController.

有一种更简单的方法可以从故事板加载 xib。假设您的控制器属于 MyClassController 类型,它继承自UIViewController.

You add a UIViewControllerusing IB in your storyboard; change the class type to be MyClassController. Delete the view that had been automatically added in the storyboard.

UIViewController在你的故事板中添加一个using IB;将类类型更改为 MyClassController。删除已自动添加到故事板中的视图。

Make sure the XIB you want called is called MyClassController.xib.

确保您要调用的 XIB 名为 MyClassController.xib。

When the class will be instantiated during the storyboard loading, the xib will be automatically loaded. The reason for this is due to default implementation of UIViewControllerwhich calls the XIB named with the class name.

当故事板加载期间类将被实例化时,xib 将自动加载。这样做的原因是因为它的默认实现UIViewController调用了以类名命名的 XIB。

回答by Hamza

This is not really an answer, but I think it is helpful to share this approach.

这不是真正的答案,但我认为分享这种方法很有帮助。

Objective-C

目标-C

  1. Import CustomViewWithXib.hand CustomViewWithXib.mto your project
  2. Create the custom view files with the same name (.h / .m / .xib)
  3. Inherit your custom class from CustomViewWithXib
  1. CustomViewWithXib.hCustomViewWithXib.m导入 您的项目
  2. 创建具有相同名称的自定义视图文件 (.h / .m / .xib)
  3. 从 CustomViewWithXib 继承您的自定义类

Swift

迅速

  1. Import CustomViewWithXib.swiftto your project
  2. Create the custom view files with the same name (.swift and .xib)
  3. Inherit your custom class from CustomViewWithXib
  1. CustomViewWithXib.swift导入您的项目
  2. 创建具有相同名称的自定义视图文件(.swift 和 .xib)
  3. 从 CustomViewWithXib 继承您的自定义类

Optional :

可选的 :

  1. Go to your xib file, set the owner with your custom class name if you need to connect some elements (for more details see the part Make the Swift file the owner of @Suragch answer's)
  1. 转到您的 xib 文件,如果您需要连接某些元素,请使用您的自定义类名设置所有者(有关更多详细信息,请参阅使 Swift 文件成为@Suragch 答案的所有者部分)

It's all, now you can add your custom view into your storyboard and it will be shown :)

就是这样,现在您可以将自定义视图添加到故事板中,它将显示出来:)

CustomViewWithXib.h:

CustomViewWithXib.h:

 #import <UIKit/UIKit.h>

/**
 *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
 MyCustomView.h
 MyCustomView.m
 MyCustomView.xib
 */

// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView

@end

CustomViewWithXib.m:

CustomViewWithXib.m:

#import "CustomViewWithXib.h"

@implementation CustomViewWithXib

#pragma mark - init methods

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

#pragma mark - setup view

- (UIView *)loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    //  An exception will be thrown if the xib file with this class name not found,
    UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
    return view;
}

- (void)commonSetup {
    UIView *nibView = [self loadViewFromNib];
    nibView.frame = self.bounds;
    // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
    nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // Adding nibView on the top of our view
    [self addSubview:nibView];
}

@end

CustomViewWithXib.swift:

CustomViewWithXib.swift

import UIKit

@IBDesignable
class CustomViewWithXib: UIView {

    // MARK: init methods
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        commonSetup()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        commonSetup()
    }

    // MARK: setup view 

    private func loadViewFromNib() -> UIView {
        let viewBundle = NSBundle(forClass: self.dynamicType)
        //  An exception will be thrown if the xib file with this class name not found,
        let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
        return view as! UIView
    }

    private func commonSetup() {
        let nibView = loadViewFromNib()
        nibView.frame = bounds
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        // Adding nibView on the top of our view
        addSubview(nibView)
    }
}

You can find some examples here.

您可以在此处找到一些示例。

Hope that helps.

希望有帮助。