如何在不使用 Nib 的情况下实例化和调用 UIView 子类

Posted

技术标签:

【中文标题】如何在不使用 Nib 的情况下实例化和调用 UIView 子类【英文标题】:How to Instantiate and Call UIView Subclasses Without Using a Nib 【发布时间】:2010-02-05 11:12:25 【问题描述】:

不使用 Interface builder 或 xib 文件,实例化从 UIView 继承的两个类的正确方法是什么,以便它们可以使用位于视图本身的 UIButtons 在它们之间切换?

我认为这涉及从应用程序委托设置 UIViewController 并将实现 UIView 的类的两个实例添加到控制器中(可能从控制器内部?)

我也不确定如何从自定义 UIViews 上的 UIButtons 引发事件以切换视图。我怀疑我需要向视图控制器添加一个方法,但我不确定如何从我的 UIView 范围内获取对视图控制器的引用。

另外,我想知道,如果需要使用 UIViewController,switch 方法是否应该在主应用委托的范围内?

一些代码示例会很棒!

【问题讨论】:

【参考方案1】:

您的主要问题是您从概念上不了解 UIViewControllers 与 UIViews 的作用。大多数人在刚开始时都不会这样做。

视图是愚蠢的,理想情况下,它们应该由通用对象组成。它们几乎不包含接口的任何逻辑。他们不知道也不关心其他观点的存在。您放入视图的唯一逻辑是与视图本身的即时和通用功能相关的逻辑,而不管它显示的数据或应用程序其他部分的状态如何。你很少需要继承 UIView。这就是为什么无需任何代码就可以在 Interface builder 中完全配置视图的原因。

ViewControllers 包含接口的逻辑并将接口连接到数据(但它们不包含或逻辑操作数据)。它们是“智能的”和高度定制的。 viewController 确实了解视图在应用程序上下文中的位置。 viewControllers 从 nib 或以编程方式加载和配置视图。 viewControllers 控制视图何时显示或隐藏以及它的顺序。 viewControllers 确定响应事件采取什么动作以及在哪里显示什么数据。

VictorB 的示例代码显示了这一切是如何务实地完成的。需要注意的重要一点是 viewController 和 view 是来自两个完全独立的类的完全独立的对象。没有重叠,也不需要继承 UIView。所有的自定义都在控制器中。

这一切都是因为 MVC 设计模式。它将接口与数据模型分离,使它们既模块化又相互独立。这使得设计、调试和重用每个独立模块变得容易。

【讨论】:

@TechZen 这是否意味着我不应该继承 UIView 并在其中实现自定义核心图形 drawrect 代码?例如,想象一下国际象棋棋盘和菜单屏幕的另一个视图。板子绘图代码是否属于 UIViewController? 实际上绘制视图是视图的一部分,应该在那里实现。决定绘制什么,绘制多少等应该是视图控制器的功能,它传递来自数据模型的数据。在国际象棋游戏的情况下,您可能在视图中有一个自定义绘制方法来处理实际的棋盘本身,但棋子及其位置将由视图控制器和数据模型负责。视图应尽可能通用且尽可能少。例如,如果您有一个为国际象棋游戏绘制棋盘的视图,您应该能够在跳棋游戏中使用它。【参考方案2】:

如果您想在代码中完成它,这里有一个我刚刚使用延迟加载的 UI 元素鼓吹的示例。我只在这里制作一个按钮并在任何一个处于活动状态的视图之间交换它。这有点尴尬,但它减少了演示这一点所需的代码量。

我创建了两个 UIView 来表示您的自定义类,一个具有蓝色背景,一个具有红色背景。按钮在两者之间交换。如果每个自定义视图中已经有一个唯一的按钮,则只需将这些按钮公开为 UIView 子类的属性,以便视图控制器可以访问它们,或者将视图控制器添加为从内部按钮操作的目标你的 UIView 的加载代码。

我已经在我的模拟器中测试了这段代码,它似乎工作正常,但你真的应该试着理解这里发生了什么,这样你就可以自己实现它。

ToggleViewController.h:

#import <UIKit/UIKit.h>
@interface ToggleViewController : UIViewController 
    UIView *firstView;
    UIView *secondView;
    UIButton *button;

- (void)addButton;
- (void)toggleViews;
@property (nonatomic, readonly) UIView* firstView;
@property (nonatomic, readonly) UIView* secondView;
@property (nonatomic, readonly) UIButton* button;
@end

ToggleViewController.m:

#import "ToggleViewController.h"
@implementation ToggleViewController

// assign view to view controller
- (void)loadView 
    self.view = self.firstView;


// make sure button is added when view is shown
- (void)viewWillAppear:(BOOL)animated 
    [self addButton];



// add the button to the center of the view
- (void)addButton 
    [self.view addSubview:self.button];
    button.frame = CGRectMake(0,0,150,44);
    button.center = self.view.center;


// to toggle views, remove button from old view, swap views, then add button again
- (void)toggleViews 
    [self.button removeFromSuperview];
    self.view = (self.view == self.firstView) ? self.secondView : self.firstView;
    [self addButton];


// generate first view on access
- (UIView *)firstView 
    if (firstView == nil) 
        firstView = [[UIView alloc] initWithFrame:CGRectZero];
        firstView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        firstView.backgroundColor = [UIColor redColor];
    
    return firstView;


// generate second view on access
- (UIView *)secondView 
    if (secondView == nil) 
        secondView = [[UIView alloc] initWithFrame:CGRectZero];
        secondView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        secondView.backgroundColor = [UIColor blueColor];
    
    return secondView;


// generate button on access
- (UIButton *)button 
    if (button == nil) 

        // create button
        button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];

        // set title
        [button setTitle:@"Toggle Views" 
                forState:UIControlStateNormal];

        // set self as a target for the "touch up inside" event of the button
        [button addTarget:self 
                   action:@selector(toggleViews) 
         forControlEvents:UIControlEventTouchUpInside];
    
    return button;


// clean up
- (void)dealloc 
    [button release];
    [secondView release];
    [firstView release];
    [super dealloc];


@end

【讨论】:

@Victorb 谢谢这是很棒的东西。顺便说一句,我不得不使用 initWithFrame:[[UIScreen mainScreen] applicationFrame] 我的 firstView 方法而不是 CGRectZero 让它在模拟器中工作有什么想法吗? 在我的测试中,我将 VC 添加到标签栏控制器,因此框架已经建立。我的猜测是,您使用的任何容器都没有设置正确的框架。由于设置了 autoresizingMask,因此 CGRectZero 框架应在其子视图布局时由父视图更改。但是,如果您的框架可以按照自己的方式工作,那就去吧。【参考方案3】:

使用界面生成器。这是有原因的。

【讨论】:

公平地说,有很多理由避免使用 Interface Builder。如果我能提供帮助,我自己从不使用它,并且知道其他有同样感受的开发人员。在开发和调试代码中包含所有应用程序行为是有道理的。

以上是关于如何在不使用 Nib 的情况下实例化和调用 UIView 子类的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用视图控制器的情况下使用 nib 文件加载视图

如何在不“连接”它们的情况下以编程方式访问 NIB 中的 UI 元素?

Swift 中的 NSWindowController。使用 Nib 进行子类化和初始化

WCF反序列化如何在不调用构造函数的情况下实例化对象?

无需 nib 以编程方式实例化 UIViewController

如何在不将控制器添加到视图层次结构并使其可见的情况下从 UIViewController 获取 UIView?