如何使用一个共同的目标对象来处理多个视图的动作/出口?
Posted
技术标签:
【中文标题】如何使用一个共同的目标对象来处理多个视图的动作/出口?【英文标题】:How to use a common target object to handle actions/outlets of multiple views? 【发布时间】:2011-08-05 02:08:17 【问题描述】:我开始尝试在以前使用代码的地方使用 IB。我已经为 iPad/iPhone 纵向/横向创建了笔尖,所以我有四种可能的视图。所有人都将文件所有者设置为 UIViewController,所有人都有一个从调色板集添加到我的 NSObject 子类“TargetObject”的对象。设置 IBActions/IBOutlets 没有问题,只需像往常一样按住 ctrl 并拖动以在一个 nib 中创建代码,然后右键单击对象并将操作/出口拖动到其他 nib 中的右侧控件,以便连接存在于所有笔尖。这个想法是应用程序委托为当前设备启动适当的视图控制器,然后该视图控制器根据方向加载正确的视图。
(也许通过自动调整大小掌握这种方法是没有必要的 - 我想知道是否可以这样做,因为仅使用那些 IB 控件来重新排列布局是非常非常令人沮丧的。)
这些一对多视图和目标对象背后的想法是我可以在应用程序的任何地方使用它们。例如,我显示的东西是核心数据中的一个对象 我想允许在应用程序的多个位置查看/编辑详细信息。这似乎是一种非常 MVC 的做事方式。
当我这样做时,问题出现在视图控制器的 loadView 中:
NSArray *portraitViewArray = [[NSBundle mainBundle] loadNibNamed:@"Weekview_iPad_Portrait" owner:self options:nil];
portraitWeekView = [portraitViewArray objectAtIndex:0];
NSArray *landscapeViewArray = [[NSBundle mainBundle] loadNibNamed:@"Weekview_iPad_Landscape" owner:self options:nil];
landscapeWeekView = [landscapeViewArray objectAtIndex:0];
// this object is intended to be a common target for portrait and landscape arrays.
weekViewTarget = [portraitViewArray objectAtIndex:1];
UIInterfaceOrientation interfaceOrientation = self.interfaceOrientation;
if(interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
self.view = portraitWeekView;
else
self.view = landscapeWeekView;
正如您所猜想的那样,纵向时一切正常,但横向时与目标对象的任何交互都会崩溃。 weekViewTarget 对象指的是使用纵向视图创建的对象,因此横向视图正在寻找一个没有被引用持有的不同实例。在横向视图中 _viewDelegate 为零 - 它是私有的,所以我无法在代码中修复它。
我也可以通过引用保持横向目标对象,但是每当方向改变时都会出现问题 - 在任何有状态控件(如 UITableView)的情况下,数据源/委托将改变,从而导致奇怪的用户旋转体验。除非我确保目标对象只实现简单的操作并使用单独的共享对象来为所需的任何表视图或文本视图实现数据源/委托对象。
我想到的一件事是使目标成为单例。似乎是个 hack,有时会无法正常工作。
什么是创建多个视图的正确方法,这些视图使用一个公共目标对象实现为 nib 以对模型进行处理?
progress - 如果我给每个目标一个指向它自己类型的指针,事情就会开始工作:
WeekViewTarget *forwardTarget;
并让真正的处理程序的forwardTarget = nil
,而未使用的处理程序的forwardTarget设置如下:
weekViewTarget = [portraitViewArray objectAtIndex:1];
forwardedWeekViewTarget = [landscapeViewArray objectAtIndex:1];
forwardedWeekViewTarget.forwardTarget = weekViewTarget;
然后在每个 IBAction 中执行以下操作:
- (IBAction)timeBlockTapped:(id)sender
if(forwardTarget != nil)
[forwardTarget performSelector:@selector(timeBlockTapped:) withObject:sender];
else
NSLog(@"Got a tap event with sender %@", [sender description]);
但还是觉得不对。
【问题讨论】:
完全不需要为横向/纵向旋转设计单独的xib。你只需要为 iPhone 和 iPad 分开不同的 xib。您能否发布崩溃时控制台中显示的错误消息? 崩溃是已知的,因为我没有挂在 loadNibNamed 返回的 NSObject 上:对于 LandscapeViewArray,就像我对 PortraitViewArray 一样。现在已经完成了,这一切都在使用原始帖子中“ - 进度”之后描述的更改。它仍然比我认为的要困难和复杂一些。离散纵向/横向笔尖的替代方案是什么 - 使用支柱和弹簧?到目前为止还没有取得很大的成功。 【参考方案1】:亚当,听起来你想要一个代理(又名外部)对象。
你的问题
NSBundle 和 UINib 在从 NIB 实例化对象图时返回自动释放的数组。当数组在事件循环结束时被释放时,您没有特别保留的任何对象都将被释放。显然,nib 内相互引用的对象只要它们的所有者被保留,就会被保留。
所以你的动作目标对象的第二个实例正在被释放,因为你没有挂在它上面。
您真正想要的只是两个 NIB 都可以引用的目标对象的单个实例。因此,您不能让您的 NIB 实例化该对象的实例。但是您的 NIB 需要能够引用该对象实例!
天哪,怎么办?!
解决方案:代理/外部对象
基本上,外部对象是您放置在 NIB 中的代理。然后,您输入实例化 NIB 的对象图时的实际实例。 NIB 加载器将 NIB 中的代理替换为您外部提供的实例,并配置您在 Interface Builder 中分配给代理的任何目标或出口。
怎么做
-
在 Interface Builder 中拖入“外部对象”而不是 NSObject 实例。它将与 Xcode 4.x 中的 File's Owner 一起出现在上部。
选择它并在“Identity Inspector”中将其类设置为适当的类型。
在“属性检查器”中,将标识符字符串设置为“TargetObject”(或您想在下面的代码中使用的任何字符串)。
利润!
代码
id owner = self; // or whatever
id targetObject = self.targetObject; // or whatever...
// Construct a dictionary of objects with strings identifying them.
// These strings should match the Identifiers you specified in IB's
// Attribute Inspector.
NSDictionary *externalObjects = [NSDictionary dictionaryWithObjectsAndKeys:
targetObject, @"TargetObject",
nil];
// Load the NIBs ready for instantiation.
UINib *portraitViewNib = [UINib nibWithNibName:@"Weekview_iPad_Portrait" bundle:nil];
UINib *landscapeViewNib = [UINib nibWithNibName:@"Weekview_iPad_Landscape" bundle:nil];
// Set up the NIB options pointing to your external objects dictionary.
NSDictionary *nibOptions = [NSDictionary dictionaryWithObjectsAndKeys:
externalObjects, UINibExternalObjects,
nil];
// Instantiate the objects in the nib passing in the owner
// (coincidentally also a proxy in the NIB) and your options dictionary.
NSArray *portraitViewArray = [portraitViewNib instantiateWithOwner:owner
options:nibOptions];
NSArray *landscapeViewArray = [landscapeViewNib instantiateWithOwner:owner
options:nibOptions];
self.view = [(UIInterfaceOrientationIsPortrait(interfaceOrientation)) ? portraitViewArray : landscapeViewArray) objectAtIndex:0];
注意事项
您正在使用 NSBundle 加载 NIB。您应该使用 UINib。读取 NIB 文件要快得多。此外,它将 NIB 加载与对象实例化分开。这意味着您可以在 init、awakeFromNib 或 viewDidLoad 中加载 NIB,而不是实例化 NIB 的内容。然后在最后可能的时刻,当您需要 NIB 中的实际对象时,您可以调用 instantiateWithOwner。
假设在使用 UITableViewController 的情况下,您将在 viewDidLoad 中加载表格单元格 nib,但在需要 -tableView:cellForRowAtIndexPath: 中的该类型单元格之前不会实际实例化它们。
【讨论】:
太棒了——正是我想做的。 这方面的文档很少(无论如何我都能找到)。谢谢 哇,我以前从未听说过这个。太美了。【参考方案2】:在 interfaceOrientation 的基础上,您可以识别方向并重置 xib 中控件的框架。 For more, this will guide you...
【讨论】:
是的,知道这一切,这里的目的是避免在任何地方使用静态、预编译器常量或幻数来重新定位子视图。如果我要定义所有东西的框架,我可以只拥有一个目标对象,给它一个字典,其中包含每个方向案例的每个视觉元素的框架,并在旋转时应用这些框架。也许更容易……但我真的只是想把它们放在笔尖上,装入正确的笔尖,然后远离数字。以上是关于如何使用一个共同的目标对象来处理多个视图的动作/出口?的主要内容,如果未能解决你的问题,请参考以下文章