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

我目前正在制作一个应用程序,将有多个计时器,这基本上都是相同的。

我想创建一个自定义类,它使用定时器的所有代码以及布局/动画,这样我就可以有5个相同的定时器,它们彼此独立操作。

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

我很难理解如何将所有东西封装到一个自定义类中,然后将其添加到视图控制器中,如果有任何帮助,我将不胜感激。

99043 次浏览

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

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

enter image description here

记住在视图控制器中使用定时器类 #import

编辑: 用于 IB 设计(代码实例化请参阅修订历史)

I'm not very familiar at all with storyboard, but I do know that you can construct your interface in IB using a .xib file 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 .xib file.

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

enter image description here

我创建了一个新的目标-C 类子类 UIView命名为“ MyCustomTimer”。在我的 .xib我设置我的 文件的所有者类为 我的客户定时器。现在我可以像其他视图/控制器一样自由地连接操作和出口。生成的 .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 .xib on my UIView subclass. Using a .xib dramatically 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:

#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

名为 loadNibNamed:owner:options:的方法完全按照它听起来的那样工作。它加载 Nib 并将“ File’s Owner”属性设置为 self。我们提取数组中的第一个对象,它是 Nib 的根视图。我们添加视图作为一个子视图,瞧,它在屏幕上。

显然,这只是改变了标签的背景颜色时,按钮,但这个例子应该让你很好的方式。

基于评论的说明:

值得注意的是,如果您遇到了无限递归问题,那么您可能忽略了这个解决方案的微妙技巧。不是你想的那样。放在故事板中的视图不会被看到,而是作为子视图加载另一个视图。它加载的视图是在笔尖中定义的视图。笔尖中的“文件所有者”就是那个看不见的视图。最酷的部分是,这个看不见的视图仍然是一个 Objective-C 类,它可以用来作为视图控制器的一种,它从笔尖带来的视图。例如,MyCustomTimer类中的 IBAction方法在视图控制器中比在视图中更为常见。

作为一个边注,一些人可能会争辩说,这打破了 MVC,我多少同意。从我的角度来看,它更接近于一个定制的 UITableViewCell,它有时也必须是部分控制器。

同样值得注意的是,这个答案是为了提供一个非常具体的解决方案; 创建一个可以在 一样的风景上实例化多次的笔尖,就像在故事板上布置的那样。例如,你可以很容易地想象这些计时器中的六个同时出现在 iPad 屏幕上。如果您只需要为视图控制器指定一个视图,该视图控制器将在应用程序中多次使用,那么 Jyavenard 为这个问题提供的解决方案几乎肯定是更好的解决方案。

Answer for view controllers, 不是风景:

有一种从故事板加载 xib 的简单方法。 Say your controller is of MyClassController type which inherit from UIViewController.

在故事板中使用 IB 添加 UIViewController; 将类类型更改为 MyClassController。删除在故事板中自动添加的视图。

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

当类在情节串连板加载期间被实例化时,xib 将被自动加载。 这是由于 UIViewController的默认实现,它调用以类名命名的 XIB。

迅速的例子

更新了 Xcode 10和 Swift 4(据报道仍然适用于 Xcode 12.4/Swift 5)

下面是一个基本的步骤。我最初从看 这个 Youtube 视频系列节目中学到了很多这方面的东西。后来我根据 这篇文章更新了我的答案。

添加自定义视图文件

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

  • . xib 文件来包含布局
  • 。快速文件作为 UIView子类

The details for adding them are below.

锡伯档案

添加一个.xib 文件到您的项目 (文件 > 新建 > 文件... > 用户界面 > 视图)。我调用我的 ReusableCustomView.xib

创建希望自定义视图具有的布局。作为一个示例,我将使用 UILabelUIButton制作一个布局。这是一个好主意,使用自动布局,以便事情将自动调整大小,无论你设置它的大小以后。(我在 Attritribute 检查器中使用 自由形式作为 xib 的大小,这样我就可以调整模拟的度量,但这是不必要的。)

enter image description here

快速档案

添加一个。快速文件到您的项目 (File > New > File... > Source > Swift File)。这是一个子类的 UIView和我调用我的 ReusableCustomView.swift

import UIKit
class ResuableCustomView: UIView {


}

Make the Swift file the owner

回到 . xib 文件,点击文档大纲中的“文件所有者”。在“身份检查器”中,将您的 。迅速的文件的名称作为自定义类名称。

enter image description here

添加自定义视图代码

用以下代码替换 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
}
}

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

连接渠道和行动

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

使用自定义视图

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

enter image description here

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

目标 C

  1. CustomViewWithXib.hCustomViewWithXib.m导入到 项目
  2. 创建同名的自定义视图文件(. h/. m/ 。 xib)
  3. 从 CustomViewWithXib 继承您的自定义类

斯威夫特

  1. CustomViewWithXib.swift导入到项目
  2. 创建名称相同的自定义视图文件(. swift 和. xib)
  3. 从 CustomViewWithXib 继承您的自定义类

Optional :

  1. 转到您的 xib 文件,设置您的自定义类名的所有者,如果您 需要连接一些元素(有关更多细节,请参见部分 the Swift file the owner of @Suragch answer's)

现在你可以把你的自定义视图添加到你的故事板中,它会显示出来:)

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 :

#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 :

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)
}
}

你可以找到一些例子 给你

希望能帮上忙。