我试着做一些复杂的事情,但应该是可能的。所以这里有一个对你们所有专家的挑战(这个论坛是由你们很多人组成的:))。
我正在创建一个问卷“组件”,我想加载在一个navigationcontroller(我的QuestionManagerViewController)。“组件”是一个“空的”UIViewController,它可以根据需要回答的问题加载不同的视图。
我的做法是:
Create Question1View object as a UIView subclass, defining some IBOutlets.
Create (using Interface Builder) the Question1View.xib (HERE IS WHERE MY PROBLEM PROBABLY IS). I set both the UIViewController and the UIView to be of class Question1View.
I link the outlets with the view's component (using IB).
I override the initWithNib of my QuestionManagerViewController to look like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
当我运行代码时,我得到这个错误:
2009-05-14 15:05:37.152 iMobiDines[17148:20b] ***由于未捕获异常'NSInternalInconsistencyException'而终止应用程序,原因:'-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "Question1View" nib but the view outlet was not set.'
我确信有一种方法可以使用nib文件加载视图,而不需要创建一个viewController类。
我做了一个我喜欢的分类:
UIView + NibInitializer.h
#import <UIKit/UIKit.h>
@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end
UIView + NibInitializer.m
#import "UIView+NibInitializer.h"
@implementation UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
if (!nibNameOrNil) {
nibNameOrNil = NSStringFromClass([self class]);
}
NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
owner:self
options:nil];
for (id view in viewsInNib) {
if ([view isKindOfClass:[self class]]) {
self = view;
break;
}
}
return self;
}
@end
然后,像这样调用:
MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];
如果你的nib不是你的类名,请使用一个nib名。
为了在你的子类中覆盖它以获得额外的行为,它可以看起来像这样:
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
self = [super initWithNibNamed:nibNameOrNil];
if (self) {
self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
}
return self;
}
Swift用户可设计选项:
Create a custom UIView subclass and a xib files, that we will name after our own class name: in our case MemeView. Inside the Meme View class remember to define it as designable with the @IBDesignable attribute before the class declaration
Rember to set the File’s Owner in the xib with our custom UIView subclass in Indetity Inspector panel
In the xib file now we can build our interface, make constraints, create outlets, actions etc.
We need to implement few methods to our custom class to open the xib once initialized
class XibbedView: UIView {
weak var nibView: UIView!
override convenience init(frame: CGRect) {
let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
self.init(nibName: nibName)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
let nib = loadNib(nibName)
nib.frame = bounds
nib.translatesAutoresizingMaskIntoConstraints = false
addSubview(nib)
nibView = nib
setUpConstraints()
}
init(nibName: String) {
super.init(frame: CGRectZero)
let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
let nib = loadNib(nibName)
nib.frame = bounds
nib.translatesAutoresizingMaskIntoConstraints = false
addSubview(nib)
nibView = nib
setUpConstraints()
}
func setUpConstraints() {
["V","H"].forEach { (quote) -> () in
let format = String(format:"\(quote):|[nibView]|")
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
}
}
func loadNib(name: String) -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: name, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
In our custom class we can also define some inspecatable properties to have full control over them from interface builder
@IBDesignable
class MemeView: XibbedView {
@IBInspectable var memeImage: UIImage = UIImage() {
didSet {
imageView.image = memeImage
}
}
@IBInspectable var textColor: UIColor = UIColor.whiteColor() {
didSet {
label.textColor = textColor
}
}
@IBInspectable var text: String = "" {
didSet {
label.text = text
}
}
@IBInspectable var roundedCorners: Bool = false {
didSet {
if roundedCorners {
layer.cornerRadius = 20.0
clipsToBounds = true
}
else {
layer.cornerRadius = 0.0
clipsToBounds = false
}
}
}
@IBOutlet weak var label: UILabel!
@IBOutlet weak var imageView: UIImageView!
}
一些例子:
如果我们需要在一个故事板或另一个xib中显示视图时添加更多信息,为此我们可以实现prepareForInterfaceBuilder(),此方法仅在接口生成器中打开文件时执行。
如果你做了我写的所有事情,但没有任何工作,这是一种通过在实现中添加断点来调试单个视图的方法。
这是视图层次结构。
希望这对您有所帮助,完整的示例可以在这里下载
下面是一种在Swift中实现的方法(目前正在XCode 7 beta 5中编写Swift 2.0)。
从你在接口构建器中设置为“自定义类”的UIView子类中创建一个像这样的方法(我的子类叫做RecordingFooterView):
class func loadFromNib() -> RecordingFooterView? {
let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
let nibObjects = nib.instantiateWithOwner(nil, options: nil)
if nibObjects.count > 0 {
let topObject = nibObjects[0]
return topObject as? RecordingFooterView
}
return nil
}
然后你可以像这样调用它:
let recordingFooterView = recordingFooterView . loadfromnib ()
你不应该在Interface Builder中将视图控制器的类设置为UIView的子类。这肯定是你的部分问题。保留它为UIViewController,它的子类,或者其他自定义类。
As for loading only a view from a xib, I was under the assumption that you had to have some sort of view controller (even if it doesn't extend UIViewController, which may be too heavyweight for your needs) set as the File's Owner in Interface Builder if you want to use it to define your interface. I did a little research to confirm this as well. This is because otherwise there would be no way to access any of the interface elements in the UIView, nor would there be a way to have your own methods in code be triggered by events.
If you use a UIViewController as your File's Owner for your views, you can just use initWithNibName:bundle: to load it and get the view controller object back. In IB, make sure you set the view outlet to the view with your interface in the xib. If you use some other type of object as your File's Owner, you'll need to use NSBundle's loadNibNamed:owner:options: method to load the nib, passing an instance of File's Owner to the method. All its properties will be set properly according to the outlets you define in IB.
在Swift 4中以编程方式从nib/xib加载视图:
// Load a view from a Nib given a placeholder view subclass
// Placeholder is an instance of the view to load. Placeholder is discarded.
// If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {
let nib = loadNib(givenNibName, placeholder: placeholderView)
return instantiateView(fromNib: nib, placeholder: placeholderView)
}
// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
//1. Load and unarchive nib file
let nibName = givenNibName ?? String(describing: type(of: placeholderView))
let nib = UINib(nibName: nibName, bundle: Bundle.main)
return nib
}
// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
//1. Get top level objects
let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)
//2. Have at least one top level object
guard let firstObject = topLevelObjects.first else {
fatalError("\(#function): no top level objects in nib")
}
//3. Return instantiated view, placeholderView is not used
let instantiatedView = firstObject as! T
return instantiatedView
}
花了好几个小时后,我想出了以下解决办法。
按照以下步骤创建自定义UIView。
1)创建类myCustomView继承自UIView。
2)创建名称为myCustomView的.xib。
3)在你的.xib文件中改变UIView的类,在那里分配myCustomView类。
4)创建iboutlet
5)在myCustomView * customView中加载.xib。
使用下面的示例代码。
myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];
注:对于那些仍然面临问题的人可以评论,我将提供他们的链接
示例项目的myCustomView