拦截/以编程方式设置 IBOutlet 属性
Posted
技术标签:
【中文标题】拦截/以编程方式设置 IBOutlet 属性【英文标题】:Intercept/Programmatically set IBOutlet properties 【发布时间】:2014-05-28 20:19:12 【问题描述】:问题:
有什么方法可以以编程方式自动设置 IBOutlet 属性(即,无需对要设置的属性进行硬编码)?也许有一些“IBOutlet 设置”例程我可以用我自己的专用代码拦截?
背景:
导致上述问题的问题源于运行以下方法时未设置“IBOutleted”大小约束(宽度和高度)(这是一种将 IB 中的“占位符”视图替换为真实视图的方法)查看):
+ (UIView*) replaceWithNibViewIfPlaceholder:(UIView*)view
BOOL isPlaceholder = ([[view subviews] count] == 0);
// Special treatment for buttons (which contain their title label, and thus
// always have one subview):
if ([view isKindOfClass:[UIButton class]] && [[view subviews] count] == 1)
isPlaceholder = [[[view subviews] firstObject] isKindOfClass:[UILabel class]];
if (isPlaceholder)
// We're assuming that there only is one root view, and that it is of the correct type:
UIView* replacer = [[view class] loadFromNib];
// We don't need to set the frame nor autoresizingMask as we're utilizing auto layout.
replacer.tag = view.tag;
replacer.alpha = view.alpha;
replacer.hidden = view.hidden;
// Copy intrinsic constraints (i.e. size constraints, which are only associated with the view itself):
[[view constraints] enumerateObjectsUsingBlock:^(NSLayoutConstraint* constraint, NSUInteger idx, BOOL *stop)
// If the constraint is not a size constraint, continue loop:
if ((constraint.firstAttribute != NSLayoutAttributeWidth &&
constraint.firstAttribute != NSLayoutAttributeHeight &&
constraint.firstAttribute != NSLayoutAttributeNotAnAttribute) ||
(constraint.secondAttribute != NSLayoutAttributeWidth &&
constraint.secondAttribute != NSLayoutAttributeHeight &&
constraint.secondAttribute != NSLayoutAttributeNotAnAttribute))
return;
NSLayoutConstraint* constraintClone = [NSLayoutConstraint constraintWithItem:replacer attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:nil attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant];
// Now add the width or height constraint:
[replacer addConstraint:constraintClone];
];
return replacer;
return view;
这个方法是从 UIView::awakeAfterUsingCoder:(NSCoder*)coder 调用的。它已经用许多不同的笔尖进行了测试,到目前为止效果很好。但是,现在的问题是我必须重新创建那些与被替换视图严格相关的约束,即宽度和高度(与超级视图相关的约束被无缝转移)。我有一个用于此类约束的 IBOutlet,并且在通过此方法时它仍然为零。
为了澄清,代码:
[replacer addConstraint:constraintClone];
工作正常,添加并应用了约束。但是,未设置相应的 IBOutlet(保持为零)。
更新:
Sashas 的回答是正确的,但是拦截 IBOutlet 分配的方法并没有解决我的问题。
正如 Sasha 所指出的,我的背景部分很不清楚。因此,我将快速尝试以不同的方式解释它。
我习惯在 Nib 文件中存储或多或少复杂的视图。为了将它们无缝地插入到故事板或其他 nib 文件中,我实现了一个“NibLoadedView”类,它基本上用复杂视图替换了来自 initWithCoder 的任何实例。换句话说,我可以在 storyboard/IB 中设置一个简单的占位符 UIView 的自定义类型,并在应用程序运行时加载真实/复杂的视图。应用于该占位符视图的所有约束都应该移动到真实视图。他们做到了,至少所有的约束都表达了占位符与其周围环境(其他视图)之间的关系。另一方面,大小约束存储在占位符视图中,如果不转移到真实视图中,将会丢失。正是这种转移,我遇到了问题,因为一旦我复制了约束,它们就会按预期应用,但是如果我将其中一个引用为 IBOutlet,则 IBOutlet 将变为 nil(它指向与占位符视图,一旦删除了所有约束的视图,弱 IBOutlet 变为 nil;强 IBOutlet 也不会改变任何东西,它只会持有错误的约束而不是 nil。
解决方案是替换:
[replacer addConstraint:constraintClone];
与:
memcpy((__bridge void*)constraint, (__bridge const void*)constraintClone, malloc_size((__bridge const void *)constraint));
[replacer addConstraint:constraint];
这将使用 constraintClone 覆盖其在内存中的位置的约束,这样会隐式更新 IBOutlet,无论它是在哪里设置的。
【问题讨论】:
你不能使用该插座属性的设置器吗? @CrimsonChris 我可以,但这不是我在问题中要求的“自动方式”。此实现是许多不同组件视图的超类。 【参考方案1】:IBOutlet
s 是通过 KVC 设置的:当故事板或 nib 被解码时,它会为所有出口(动作、出口集合)调用 setValue:forKey:
。如果出于某种原因,您想干预此过程,请覆盖它并在key
正确时使用您的自定义逻辑。
也许您想查看awakeFromNib
- 因为这是完全解码 nib 并设置所有插座的第一种方法。老实说,我不是很了解目标,也许你可以解释一下。
【讨论】:
【参考方案2】:你太聪明了。用memcpy
覆盖对象的内存充其量是有风险的,而覆盖不透明的对象则更糟。您这样做的方式可能会导致泄漏、弱引用出错等,而这只是美好的一天。
说真的,不要这样做。
如果我正确理解您的问题,您有 (a) 占位符视图和 (b) IBOutlet
引用该占位符视图的约束。您希望 (1) 替换占位符视图并 (2) 更新 IBOutlet
以引用替换视图上的约束。而且您正在放置您不想知道引用您的约束的任何 IBOutlet(s) 在哪里的进一步限制。
考虑一般意义上的间接。
您可以按照以下思路构建模型:
创建一个管理对约束的引用的控制器对象。 向占位符视图添加一个引用其控制器对象的属性。 将控制器管理的初始约束设置为占位符 将所有 IBOutlet 指向控制器而不是对象的约束。 当您使用另一个视图替换占位符时,使用它到控制器对象的链接更新被引用的约束。一切正常,毫无疑问。
NSObjectController
支持这种模型。
请勿阅读本文
如果你真的想给自己和你的用户开枪,不要想着覆盖,想着交换,当然还是很危险的。说得够多了。
【讨论】:
以上是关于拦截/以编程方式设置 IBOutlet 属性的主要内容,如果未能解决你的问题,请参考以下文章
强 vs 弱 - 如何定义可能连接或不连接到 IBOutlet 的属性?
iPhone XCode 编程:从导航视图调用的视图未设置其 IBOutlet 属性