拦截/以编程方式设置 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】:

IBOutlets 是通过 KVC 设置的:当故事板或 nib 被解码时,它会为所有出口(动作、出口集合)调用 setValue:forKey:。如果出于某种原因,您想干预此过程,请覆盖它并在key 正确时使用您的自定义逻辑。

也许您想查看awakeFromNib - 因为这是完全解码 nib 并设置所有插座的第一种方法。老实说,我不是很了解目标,也许你可以解释一下。

【讨论】:

【参考方案2】:

你太聪明了。用memcpy 覆盖对象的内存充其量是有风险的,而覆盖不透明的对象则更糟。您这样做的方式可能会导致泄漏、弱引用出错等,而这只是美好的一天。

说真的,不要这样做。

如果我正确理解您的问题,您有 (a) 占位符视图和 (b) IBOutlet 引用该占位符视图的约束。您希望 (1) 替换占位符视图并 (2) 更新 IBOutlet 以引用替换视图上的约束。而且您正在放置您不想知道引用您的约束的任何 IBOutlet(s) 在哪里的进一步限制。

考虑一般意义上的间接。

您可以按照以下思路构建模型:

创建一个管理对约束的引用的控制器对象。 向占位符视图添加一个引用其控制器对象的属性。 将控制器管理的初始约束设置为占位符 将所有 IBOutlet 指向控制器而不是对象的约束。 当您使用另一个视图替换占位符时,使用它到控制器对象的链接更新被引用的约束。

一切正常,毫无疑问。

NSObjectController 支持这种模型。

请勿阅读本文

如果你真的想给自己和你的用户开枪,不要想着覆盖,想着交换,当然还是很危险的。说得够多了。

【讨论】:

以上是关于拦截/以编程方式设置 IBOutlet 属性的主要内容,如果未能解决你的问题,请参考以下文章

强 vs 弱 - 如何定义可能连接或不连接到 IBOutlet 的属性?

IBOutlet 啥时候初始化?

如何使用 Swift 以编程方式分配 IBOutlet?

iPhone XCode 编程:从导航视图调用的视图未设置其 IBOutlet 属性

IBOutlet 用于约束并以编程方式将其连接到 UIButton

iOS 8.4 - iboutlet 未填充到以编程方式创建的视图控制器中