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

Mic*_*all 110 xcode class uiviewcontroller uiview ios

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

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

我使用IB(xcode 4.2)创建了布局,并且定时器的所有代码当前都在viewcontroller类中.

我很难将我的大脑包装成如何将所有内容封装到自定义类中,然后将其添加到viewcontroller,任何帮助都将非常感激.

NJo*_*nes 140

那么从概念上回答,你的计时器应该是一个子类UIView而不是NSObject.

要在IB中实例化一个计时器实例,只需UIView在视图控制器的视图中拖放它,然后将它的类设置为计时器的类名.

在此输入图像描述

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

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

我不是在所有与分镜很熟悉,但我不知道,你可以使用一个构建你的界面IB .xib文件,这几乎等同于用故事板的版本; 您甚至可以将整个视图从现有界面复制并粘贴到.xib文件中.

为了测试这个,我创建了一个.xib名为"MyCustomTimerView.xib" 的新空.然后我添加了一个视图,并添加了一个标签和两个按钮.喜欢如此:

在此输入图像描述

我创建了一个UIView名为"MyCustomTimer" 的新Objective-C类子类.在我的.xib设置我的文件的所有者类是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
Run Code Online (Sandbox Code Playgroud)

剩下要跳的唯一障碍是.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
Run Code Online (Sandbox Code Playgroud)

名为的方法loadNibNamed:owner:options:完全听起来像它所做的那样.它加载Nib并将"File's Owner"属性设置为self.我们提取数组中的第一个对象,这是Nib的根视图.我们将视图添加为子视图,将Voila添加到屏幕上.

显然,这只是在按下按钮时改变标签的背景颜色,但是这个例子可以帮助你顺利完成.

基于评论的备注:

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

作为旁注,有些人可能认为这种情况MVC有所破坏,我在某种程度上同意.从我的观点来看,它与自定义更紧密相关UITableViewCell,有时也必须是部分控制器.

值得注意的是,这个答案是提供一个非常具体的解决方案; 创建一个可以在故事板上布置的同一视图上多次实例化的笔尖.例如,您可以轻松地将这些计时器中的六个同时放在iPad屏幕上.如果您只需要为在整个应用程序中多次使用的视图控制器指定视图,那么jyavenard为此问题提供的解决方案几乎肯定是一个更好的解决方案.

  • 我从initWithCoder得到了无限递归.有任何想法吗? (28认同)
  • 无限递归的另一个原因是将xib中的根视图设置为自定义类.您不想这样做,将其保留为默认的"UIView" (20认同)
  • 只有一件事能引起我对这种方法的注意......现在,2个视图在同一个类中是不是很麻烦?原始文件.h/.m继承自UIView是第一个并通过做`[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView"owner:self options:nil] objectAtIndex:0]];`添加第二次UIView ...这种看起来很奇怪,没有其他人有同样的感觉?如果这是苹果设计的东西呢?或者这是通过这样做找到的解决方案? (11认同)
  • 很抱歉恢复旧线程,确保`File's Owner`设置为您的类名解决了我的无限递归问题. (6认同)

Sur*_*gch 124

斯威夫特的例子

针对Xcode 10和Swift 4进行了更新

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

添加自定义视图文件

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

  • .xib文件包含布局
  • .swift文件作为UIView子类

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

Xib文件

将.xib文件添加到项目中("文件">"新建">"文件...">"用户界面">"查看").我在叫我的ReusableCustomView.xib.

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

在此输入图像描述

Swift文件

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

import UIKit
class ResuableCustomView: UIView {

}
Run Code Online (Sandbox Code Playgroud)

使Swift文件成为所有者

返回.xib文件,然后单击Document Outline中的"File's Owner".在Identity Inspector中,将.swift文件的名称写为自定义类名.

在此输入图像描述

添加自定义视图代码

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

连接奥特莱斯和行动

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

使用自定义视图

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

在此输入图像描述

  • 重要的是不要将Xib视图类设置为ResuableCustomView,而是将此Xib的所有者.否则你会有错误. (29认同)
  • 如果其他人对IB中的视图渲染有问题,我发现将`Bundle.main`更改为`Bundle(for:...)`就可以了.显然,`Bundle.main`在设计时是不可用的. (6认同)
  • 是的,好提醒.我不知道我浪费了多少时间试图解决因将Xib的视图(而不是文件所有者)设置为类名而导致的无限递归问题. (4认同)
  • 这不适用于@IBDesignable的原因是你没有实现`init(frame:)`.一旦你添加了这个(并将所有初始化代码拉入某个`commonInit()`函数),它就能正常工作并在IB中显示.在此处查看更多信息:/sf/ask/2188613451/ (4认同)
  • @TomCalmon,我在Xcode 8中用Swift 3和Auto Layout重新编写了这个项目,对我来说还不错. (2认同)

jya*_*ard 39

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

有一种从故事板加载xib的简单方法.假设您的控制器是继承自的MyClassController类型UIViewController.

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

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

在故事板加载期间实例化类时,将自动加载xib.原因是默认实现UIViewController调用以类名命名的XIB.

  • 问题是视图,而不是视图控制器. (38认同)

Ham*_*mza 15

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

Objective-C的

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

迅速

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

可选的 :

  1. 如果需要连接一些元素,请转到您的xib文件,使用您的自定义类名设置所有者(有关详细信息,请参阅部分使Swift文件成为@Suragch答案的所有者)

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

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里找到一些例子.

希望有所帮助.


归档时间:

查看次数:

90850 次

最近记录:

6 年,1 月 前