我目前正在制作一个应用程序,其中将有多个计时器,它们基本上都是相同的。
我想创建一个自定义类,使用所有计时器的代码以及布局/动画,这样我就可以拥有5个相同的计时器,它们独立运行。
我使用IB(xcode 4.2)创建了布局,而所有计时器的代码目前都在viewcontroller类中。
我很难理解如何将所有内容封装到自定义类中,然后将其添加到viewcontroller中,任何帮助都将不胜感激。
我目前正在制作一个应用程序,其中将有多个计时器,它们基本上都是相同的。
我想创建一个自定义类,使用所有计时器的代码以及布局/动画,这样我就可以拥有5个相同的计时器,它们独立运行。
我使用IB(xcode 4.2)创建了布局,而所有计时器的代码目前都在viewcontroller类中。
我很难理解如何将所有内容封装到自定义类中,然后将其添加到viewcontroller中,任何帮助都将不胜感激。
已更新至Xcode 10和Swift 4(并据报道,仍可用于Xcode 12.4 / Swift 5)
下面是一个基本的步骤指南。我最初从观看这个Youtube视频系列学到了很多内容。后来我根据这篇文章更新了我的答案。
以下两个文件将构成您的自定义视图:
UIView
子类它们的添加细节如下。
xib文件
向您的项目中添加.xib文件(文件>新建>文件…>用户界面>视图)。我称之为ReusableCustomView.xib
。
创建您要自定义视图的布局。例如,我将创建一个带有UILabel
和UIButton
的布局。最好使用自动布局,这样无论您设置多大的尺寸,事物都会自动调整大小。(我在属性检查器中使用了可自由设置的xib大小,以便我可以调整模拟指标,但这不是必需的。)
swift文件
向您的项目添加.swift文件(文件>新建>文件…>源代码>Swift文件)。它是UIView
的子类,我称之为ReusableCustomView.swift
。
import UIKit
class ResuableCustomView: UIView {
}
回到您的.xib文件,并在文档大纲中单击“文件所有者”。在“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
}
}
确保您的.xib文件名称拼写正确。
通过控制从xib布局中的标签和按钮拖动到Swift自定义视图代码,连接出口和动作。
您的自定义视图现在已完成。您需要在主故事板中想放置它的地方添加一个UIView
,并在Identity Inspector中将视图的类名设置为ReusableCustomView
。
Bundle.main
更改为Bundle(for: ...)
可以解决问题。显然,在设计时Bundle.main
不可用。 - crizzisinit(frame:)
。一旦你添加了这个(并把所有的初始化代码放到 commonInit()
函数里),它就可以正常工作并在 IB 中显示了。更多信息请参考这里:https://dev59.com/Sl0Z5IYBdhLWcg3wnBVS - Dimitris从概念上讲,您的计时器应该是 UIView
的子类,而不是 NSObject
。
要在IB中实例化计时器,请简单地将一个UIView
拖到您的视图控制器的视图上,并将其类设置为您的计时器的类名。
请记得在您的视图控制器中 #import
您的计时器类。
编辑:对于IB设计(有关代码实例化,请参见修订历史记录)
我对Storyboard并不熟悉,但我知道您可以使用 .xib
文件在IB中构建界面,这与使用Storyboard版本几乎相同;您甚至可以将现有界面的整个视图复制并粘贴到 .xib
文件中。
为了测试此功能,我创建了一个名为 "MyCustomTimerView.xib" 的新空白 .xib
。然后我添加了一个视图,以及标签和两个按钮。如下所示:
我创建了一个名为 "MyCustomTimer" 的 Objective-C 类,它是 UIView
的子类。在我的 .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
唯一的障碍是将这个.xib
应用于我的UIView
子类。使用.xib
可以大幅减少设置所需的时间。并且,由于你正在使用Storyboard加载计时器,我们知道只有-(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
方法loadNibNamed:owner:options:
的作用与其名字相符。它会加载一个Nib文件并将"File's Owner"属性设置为当前对象。我们从数组中提取第一个对象,这就是Nib的根视图。然后我们将该视图添加为子视图,然后Ta就出现在屏幕上了。MyCustomTimer
类中的IBAction
方法通常更适用于视图控制器而不是视图。UITableViewCell
,有时也必须成为控制器的一部分。userInfo
属性上使用它:@property(nonatomic,copy) NSDictionary *userInfo;
- NJonesinitWithCoder
方法中的addSubView
方法之后使用_view.frame = self.frame;
来设置框架来“解决”它。 - MathewS[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]];
添加了第二个UIView...
这对我来说看起来有点奇怪,没人有同感吗?如果是,这是苹果设计的东西吗?还是别人发现的解决方案? - S.H.解答关于视图控制器,而非视图的问题:
有一种更简单的方法从故事板中加载xib。
假设你的控制器是MyClassController类型,它继承自UIViewController
。
在故事板中添加一个UIViewController
,将类类型更改为MyClassController。删除自动添加到故事板中的视图。
确保要调用的XIB命名为MyClassController.xib。
当该类在故事板加载期间实例化时,xib将自动加载。
原因是由于UIViewController
的默认实现会调用以该类名称命名的XIB。
'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''
。请注意这个奇怪的nibName。在nib中,我使用了正确的类名。 - Jeff这并不是一个真正的答案,但我认为分享这种方法很有帮助。
Objective-C
Swift
可选:
就是这样,现在你可以将你的自定义视图添加到 storyboard 中,并且它将被显示 :)
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)
}
}
你可以在这里找到一些例子。
希望能有所帮助。
UITableViewCell
或UICollectionViewCell
中使用容器视图。 在我的情况下,我需要一个小而相当复杂的视图,可以在控制器和集合视图中多次使用。 设计师们不断重新设计它,因此我希望有一个定义布局的单一位置。 因此,需要使用一个nib。 - Echelon