如何初始化自定义视图(控制器),以便它以编程方式和在 Interface Builder 中工作?

Posted

技术标签:

【中文标题】如何初始化自定义视图(控制器),以便它以编程方式和在 Interface Builder 中工作?【英文标题】:How to initialize a custom view(controller) so it works both programmatically and in Interface Builder? 【发布时间】:2012-09-21 12:57:24 【问题描述】:

假设您实现了一个自定义表格视图和一个自定义视图控制器(它主要模仿 UITableViewControllers 的行为,但是当以编程方式初始化时,...

@interface Foo : MyCustomTableViewController ...

Foo *foo = [[Foo alloc] init];

...foo.view 是类MyCustomTableView 而不是UITableView

// MyCustomTableView.h

@protocol MyTableViewDelegate <NSObject, UITableViewDelegate>

// ...

@end

@protocol MyTableViewDataSource <NSObject, UITableViewDataSource>

// ...

@end

@interface MyCustomTableView : UITableView

// ...

@end

// MyCustomTableViewController.h

@interface MyCustomTableViewController : UIViewController

// ...

@end

您应该如何以正确的顺序/方式实现/覆盖 init 方法,以便您可以通过以编程方式子类化 MyCustomTableViewController 或通过将自定义类类型设置为从任何自定义 nib 文件创建和使用 MyCustomTableView 的实例界面生成器中的MyCustomTableView

请务必注意,这正是 UITableView(主要是 UIKit)现在的工作方式:开发人员可以通过编程方式或通过 nib 创建和使用,无论是 File owner 的主要 @ 987654333@ 或更复杂层次结构中的某些子视图,只需分配数据源或委托,就可以了...

到目前为止,如果您将MyCustomTableViewController 子类化,我将成功实现此功能,我将在其中创建MyCustomTableView 的实例并在loadView 方法中将其分配给self.view;但无法弄清楚initWithNibName:bundle:initWithCoder:awakeFromNibawakeAfterUsingCoder: 或其他任何操作的方式。我迷失在生命周期链中,每次都以黑屏/黑屏告终。

谢谢。

【问题讨论】:

【参考方案1】:

UITableViewController 如何加载它的表,无论是否连接到界面生成器,这都是一个真正的谜,但是我想出了一个很好的方法来模拟这种行为。

我想通过一个包含 MKMapView 的可重用视图控制器来实现这一点,我想出了一个技巧来通过检查视图的背景颜色来实现它。

这很难的原因是因为任何对 self.view 的调用都会导致情节提要加载或加载默认的 UIView(如果不存在)。如果用户真的没有设置视图,则无法确定这两个步骤之间是否存在。所以诀窍是来自故事板的那个有颜色,默认的颜色是 nil。

所以现在我有了一个可以在代码或情节提要中使用的 mapViewController,它甚至不关心是否设置了地图。很酷。

- (void)viewDidLoad

    [super viewDidLoad];

    //magic to work without a view set in the storboard or in code.
    //check if a view has been set in the storyboard, like what UITableViewController does.
    //check if don't have a map view
    if(![self.view isKindOfClass:[MKMapView class]])
        //check if the default view was loaded. Default view always has no background color.
        if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor)
            //switch it for a map view
            self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
            self.mapView.delegate = self;
        else
            [NSException raise:@"MapViewController didn't find a map view" format:@"Found a %@", self.view.class];
        
    

【讨论】:

【参考方案2】:

我在编写此类类时使用的策略是尽可能晚地推迟我的自定义初始化代码。如果我可以等待viewDidLoadviewWillAppear 进行任何设置,而不是在initinitWithNibName:bundle: 或类似方法中编写任何自定义代码,我就会知道我的对象已像父类一样被初始化它是如何实例化的。我经常设法编写我的类而不覆盖这些 init 方法。

如果我发现我需要将我的初始化代码放在init 方法中,我的策略是只编写我的初始化代码的一个版本,将它放在一个单独的方法中,然后覆盖所有的 init 方法。被覆盖的方法调用自己的超类版本,检查是否成功,然后调用我的内部初始化方法。

如果这些策略失败了,这样类的对象的实例化方式确实会产生影响,我将为各种init 方法中的每一个编写自定义方法。

【讨论】:

【参考方案3】:

这就是我解决自己问题的方法:

- (void)loadView

    if (self.nibName) 
        // although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
        [super loadView];
    
    else 
        self.view = // ... whatever UIView you'd like to create
    

【讨论】:

以上是关于如何初始化自定义视图(控制器),以便它以编程方式和在 Interface Builder 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

如何在自定义单元格中点击以编程方式添加的 UiImageViews 推送新视图

如何以编程方式使用自动布局添加自定义视图

如何以编程方式将自定义 uiview 添加到视图控制器 SWIFT

如何在 Objective-C 控制器中以编程方式加载用 swift 编写的自定义 XIB 视图

为啥我无法在 AppDelegate 中以编程方式更改我的初始视图控制器?

iOS 使用自定义 Xib 和视图覆盖 initWithFrame 的正确方法?