加载 UIView 子类的 Nib 的正确方法

我知道以前有人问过这个问题,但是答案自相矛盾,我也很困惑,所以请不要激怒我。

我想有一个可重用的 UIView子类整个应用程序。我想使用一个 nib 文件来描述接口。

现在我们假设它是一个加载指示器视图,其中包含一个活动指示器。我希望在某个事件上实例化这个视图,并在视图控制器的视图中进行动画。我可以用编程的方式描述视图的接口,用编程的方式创建元素,并在 init 方法中设置它们的框架等等。

但是我怎么能用笔尖做到这一点呢?保持 Interface Builder 给定的尺寸,而无需设置框架。

我已经设法这样做了,但我肯定这是错误的(这只是一个视图,在它的采摘) :

 - (id)initWithDataSource:(NSDictionary *)dataSource {
self = [super init];
if (self){
self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
self.pickerViewData = dataSource;
[self configurePickerView];
}
return self;
}

但是我覆盖了 self,当我实例化它时:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
selectView.delegate = self;


selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

我必须手动设置的框架,而不是有它从 IB 拾起。

编辑: 我想在视图控制器中创建这个自定义视图,并有权控制视图的元素。我不需要新的视图控制器。

谢谢

编辑: 我不知道这是否是最佳实践,我肯定不是,但我是这样做的:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
selectView.delegate = self;
[selectView configurePickerViewWithData:ds];
selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
selectView.alpha = 0.9;
[self.view addSubview:selectView];
[UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
selectView.alpha = 1;
} completion:^(BOOL finished) {
}];

还需要正确的练习

这是否应该使用视图控制器和带有 nib 名称的 init 来完成?我是否应该在代码中设置一些 UIView 初始化方法的笔尖?还是说我的所作所为是正确的?

104642 次浏览
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

I'm using this to initialise the reusable custom views I have.


Note that you can use "firstObject" at the end there, it's a little cleaner. "firstObject" is a handy method for NSArray and NSMutableArray.

Here's a typical example, of loading a xib to use as a table header. In your file YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normally, in the TopArea.xib, you would click on File Owner and set the file owner to YourClass. Then actually in YourClass.h you would have IBOutlet properties. In TopArea.xib, you can drag controls to those outlets.

Don't forget that in TopArea.xib, you may have to click on the View itself and drag that to some outlet, so you have control of it, if necessary. (A very worthwhile tip is that when you are doing this for table cell rows, you absolutely have to do that - you have to connect the view itself to the relevant property in your code.)

Follow the following steps

  1. Create a class named MyView .h/.m of type UIView.
  2. Create a xib of same name MyView.xib.
  3. Now change the File Owner class to UIViewController from NSObject in xib. See the image below enter image description here
  4. Connect the File Owner View to your View. See the image below enter image description here

  5. Change the class of your View to MyView. Same as 3.

  6. Place controls create IBOutlets.

Here is the code to load the View:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Hope it helps.

Clarification:

UIViewController is used to load your xib and the View which the UIViewController has is actually MyView which you have assigned in the MyView xib..

Demo I have made a demo grab here

Well you could either initialize the xib using a view controller and use viewController.view. or do it the way you did it. Only making a UIView subclass as the controller for UIView is a bad idea.

If you don't have any outlets from your custom view then you can directly use a UIViewController class to initialize it.

Update: In your case:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

Otherwise you have to make a custom UIViewController(to make it as the file's owner so that the outlets are properly wired up).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Wherever you want to add the view you can use.

[parentView addSubview:yCustCon.view];

However passing the another view controller(already being used for another view) as the owner while loading the xib is not a good idea as the view property of the controller will be changed and when you want to access the original view, you won't have a reference to it.

EDIT: You will face this problem if you have setup your new xib with file's owner as the same main UIViewController class and tied the view property to the new xib view.

i.e;

  • YourMainViewController -- manages -- mainView
  • CustomView -- needs to load from xib as and when required.

The below code will cause confusion later on, if you write it inside view did load of YourMainViewController. That is because self.view from this point on will refer to your customview

-(void)viewDidLoad:(){
UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}

If you want to keep your CustomView and its xib independent of File's Owner, then follow these steps

  • Leave the File's Owner field empty.
  • Click on actual view in xib file of your CustomView and set its Custom Class as CustomView (name of your custom view class)
  • Add IBOutlet in .h file of your custom view.
  • In .xib file of your custom view, click on view and go in Connection Inspector. Here you will all your IBOutlets which you define in .h file
  • Connect them with their respective view.

in .m file of your CustomView class, override the init method as follow

-(CustomView *) init{
CustomView *result = nil;
NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
for (id anObject in elements)
{
if ([anObject isKindOfClass:[self class]])
{
result = anObject;
break;
}
}
return result;
}

Now when you want to load your CustomView, use the following line of code [[CustomView alloc] init];

Answering my own question about 2 or something years later here but...

It uses a protocol extension so you can do it without any extra code for all classes.

/*


Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank


*/


public protocol CreatedFromNib {
static func createFromNib() -> Self?
static func nibName() -> String?
}


extension UIView: CreatedFromNib { }


public extension CreatedFromNib where Self: UIView {


public static func createFromNib() -> Self? {
guard let nibName = nibName() else { return nil }
guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
return view
}


public static func nibName() -> String? {
guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
return n
}
}


// Usage:
let myView = MyView().createFromNib()

In Swift:

For example, name of your custom class is InfoView

At first, you create files InfoView.xib and InfoView.swiftlike this:

import Foundation
import UIKit


class InfoView: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Then set File's Owner to UIViewController like this:

enter image description here

Rename your View to InfoView:

enter image description here

Right-click to File's Owner and connect your view field with your InfoView:

enter image description here

Make sure that class name is InfoView:

enter image description here

And after this you can add the action to button in your custom class without any problem:

enter image description here

And usage of this custom class in your MainViewController:

func someMethod() {
var v = InfoView.instanceFromNib()
v.frame = self.view.bounds
self.view.addSubview(v)
}