更新
我刚刚注意到您的问题是在谈论NSView
(OS X),而不是UIView
(iOS)。好吧,这个想法应该仍然适用,但是您将无法将我的代码原封不动地放入您的项目中。对不起。
原来的
考虑更改您的视图层次结构。假设您的自定义边框视图称为BorderView
. 现在,您正在将子视图直接添加到并在 其子视图BorderView
之间创建约束。BorderView
相反,给它BorderView
一个单独的子视图,它在其contentView
属性中公开。将您的子视图添加到contentView
而不是直接添加到BorderView
. 然后BorderView
可以根据需要布置contentView
它。这是如何UITableViewCell
工作的。
这是一个例子:
@interface BorderView : UIView
@property (nonatomic, strong) IBOutlet UIView *contentView;
@property (nonatomic) UIEdgeInsets borderSize;
@end
如果我们使用的是 xib,那么我们遇到的问题是 IB 不知道它应该将子视图添加到contentView
而不是直接添加到BorderView
. (它确实知道这一点UITableViewCell
。)为了解决这个问题,我做了contentView
一个出口。这样,我们可以创建一个单独的顶级视图用作内容视图,并将其连接到BorderView
的contentView
插座。
为了实现BorderView
这种方式,我们需要为 theBorderView
和 its之间的四个约束中的每一个提供一个实例变量contentView
:
@implementation BorderView {
NSLayoutConstraint *topConstraint;
NSLayoutConstraint *leftConstraint;
NSLayoutConstraint *bottomConstraint;
NSLayoutConstraint *rightConstraint;
UIView *_contentView;
}
contentView
访问者可以按需创建内容视图:
#pragma mark - Public API
- (UIView *)contentView {
if (!_contentView) {
[self createContentView];
}
return _contentView;
}
并且 setter 可以替换现有的内容视图,如果有的话:
- (void)setContentView:(UIView *)contentView {
if (_contentView) {
[self destroyContentView];
}
_contentView = contentView;
[self addSubview:contentView];
}
borderSize
setter 需要安排更新约束和重绘边框:
- (void)setBorderSize:(UIEdgeInsets)borderSize {
if (!UIEdgeInsetsEqualToEdgeInsets(borderSize, _borderSize)) {
_borderSize = borderSize;
[self setNeedsUpdateConstraints];
[self setNeedsDisplay];
}
}
我们需要在drawRect:
. 我将用红色填充它:
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds];
[path appendPath:[UIBezierPath bezierPathWithRect:UIEdgeInsetsInsetRect(bounds, self.borderSize)]];
path.usesEvenOddFillRule = YES;
[path addClip];
[[UIColor redColor] setFill];
UIRectFill(bounds);
}
创建内容视图很简单:
- (void)createContentView {
_contentView = [[UIView alloc] init];
[self addSubview:_contentView];
}
销毁它稍微复杂一些:
- (void)destroyContentView {
[_contentView removeFromSuperview];
_contentView = nil;
[self removeConstraint:topConstraint];
topConstraint = nil;
[self removeConstraint:leftConstraint];
leftConstraint = nil;
[self removeConstraint:bottomConstraint];
bottomConstraint = nil;
[self removeConstraint:rightConstraint];
rightConstraint = nil;
}
updateConstraints
如果有人调用,系统会在进行布局和绘图之前自动调用setNeedsUpdateConstraints
,我们就是这样做的setBorderSize:
。在updateConstraints
中,如有必要,我们将创建约束,并根据 更新它们的常量borderSize
。我们还告诉系统不要将自动调整大小的掩码转换为约束,因为这往往会产生无法满足的约束。
- (void)updateConstraints {
self.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[super updateConstraints];
if (!topConstraint) {
[self createContentViewConstraints];
}
topConstraint.constant = _borderSize.top;
leftConstraint.constant = _borderSize.left;
bottomConstraint.constant = -_borderSize.bottom;
rightConstraint.constant = -_borderSize.right;
}
所有四个约束都是以相同的方式创建的,所以我们将使用一个辅助方法:
- (void)createContentViewConstraints {
topConstraint = [self constrainContentViewAttribute:NSLayoutAttributeTop];
leftConstraint = [self constrainContentViewAttribute:NSLayoutAttributeLeft];
bottomConstraint = [self constrainContentViewAttribute:NSLayoutAttributeBottom];
rightConstraint = [self constrainContentViewAttribute:NSLayoutAttributeRight];
}
- (NSLayoutConstraint *)constrainContentViewAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_contentView attribute:attribute relatedBy:NSLayoutRelationEqual toItem:self attribute:attribute multiplier:1 constant:0];
[self addConstraint:constraint];
return constraint;
}
@end
我在这个 git 存储库中放了一个完整的工作示例。