问题
Question:
Is there any way that I could set IBOutlet properties programmatically and in an automated way (i.e. without hard-coding the properties to be set)? Maybe there is some "IBOutlet setting" routine that I could intercept with my own specialized code?
Background:
The problem leading to the question above originates from the fact that "IBOutleted" size constraints (width and height) are not being set when running the following method (it's a method for replacing a "placeholder" view from IB with the real view):
+ (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;
}
This method is called from UIView::awakeAfterUsingCoder:(NSCoder*)coder. It has been tested with a lot of different Nibs and has so far worked perfectly well. The problem now, though, is that I have to recreate those constraints that are strictly related to the view being replaced, i.e. width and height (constraints that are related to superview are seamlessly transferred). I have an IBOutlet for one such constraint, and it remains nil when going through this method.
To clarify, the code:
[replacer addConstraint:constraintClone];
works fine, the constraint is added and applied. However, the corresponding IBOutlet is not set (remains nil).
Update:
Sashas answer was correct, but the approach of intercepting IBOutlet assignments didn't solve the issue for me.
As Sasha pointed out, my background section is quite unclear. Thus I will make a quick attempt explain it in a different way.
I use to store more or less complex views in Nib files. In order to seamlessly insert those in storyboard or other nib files, I have implemented a "NibLoadedView" class, which basically replaces whatever instance comes from initWithCoder by the complex view. In other words, I can set the custom type of a simple placeholder UIView in storyboard/IB, and that will load the real/complex view in its place when the app runs. All constraints applied to that placeholder view are supposed to move to the real view. And they did, at least all constraints that expressed a relation between the placeholder and its surroundings (other views). Size constraints, on the other hand, are stored in the placeholder view, and will get lost if not transferred to the real view. And it was this transfer that I had problems with, because once I copied the constraints, they were applied as expected, but if I referenced one of them as an IBOutlet, that IBOutlet would turn nil (it was pointing to the constraint related to the placeholder view, and once that view with all of its constraints was removed, the weak IBOutlet turned nil; a strong IBOutlet would not change anything either, it would just hold the wrong constraint instead of being nil).
The solution was to replace:
[replacer addConstraint:constraintClone];
with:
memcpy((__bridge void*)constraint, (__bridge const void*)constraintClone, malloc_size((__bridge const void *)constraint));
[replacer addConstraint:constraint];
This overwrites the constraint at its place in memory with the constraintClone, that way implicitly updating the IBOutlet, wherever and however it is set.
回答1:
IBOutlet
s are set through KVC: when storyboard or nib is being decoded it calls setValue:forKey:
for all outlets (actions, outlet collections). If, for some reason, you want to interfere with this process, override it and have your custom logic when key
is right.
Perhaps you want to look into awakeFromNib
- as it's the first method when nib was completely decoded and all outlets were set. To be honest, I do not really understand the goal, maybe you can explain it a bit more.
回答2:
You are trying to be too clever. Overwriting an object's memory with memcpy
is risky at best, overwriting an opaque object is worse. The way you are doing it could result in leaks, weak references going wrong, etc., and that is just on a good day.
Seriously, don't do it.
If I understand your problem correctly you have (a) a placeholder view and (b) an IBOutlet
referencing a constraint on that placeholder view. You wish to (1) replace the placeholder view and (2) update the IBOutlet
to reference a constraint on the replacement view. And you are placing the further restriction that you don't want to have know where any IBOutlet(s) referencing your constraint are.
Think indirection, in the general sense.
You can construct a model along the following lines:
- Create a controller object which manages a reference to a constraint.
- Add a property to your placeholder view which references its controller object.
- Set the initial constraint being managed by the controller to your placeholder
- Direct all IBOutlet to the constraint to the controller and not the object.
- When you replace the placeholder with another view using its link to the controller object update the constraint being referenced.
Everything works, nothing dubious.
This is the kind of model that NSObjectController
exists to support.
DO NOT READ THIS
If you really want to shoot yourself and your users in the foot don't think overwrite, think exchange, still dangerous of course. Enough said.
来源:https://stackoverflow.com/questions/23921071/intercept-programmatically-set-iboutlet-properties