我有一个自定义的NSView
子类,它周围有一个边框。边框绘制在此视图中。是否可以通过自动布局来尊重这个边界?
例如,当我将子视图放置到我的自定义视图并设置这样的约束时:
@"H:|-(myViewSubView)-|" (not @"H:|-(myViewBorderWidth)-(myViewSubView)-(myViewBorderWidth)-|")
@"V:|-(myViewSubView)-|"
布局必须是:
Horizontal: |-(myViewBorderWidth)-|myViewSubview|-(myViewBorderWidth)-|
Vertical: |-(myViewBorderWidth)-|myViewSubview|-(myViewBorderWidth)-|
我试图在我的视图中覆盖-bounds
方法返回没有边框的边界,但它没有帮助。
我刚刚注意到你的问题是关于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
,我们需要为BorderView
和它的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
我在this git repository中提供了一个完整的工作示例。
您是否尝试过设置内在大小以包含边框大小?
- (NSSize)intrinsicContentSize
{
return NSMakeSize(width+bordersize, height+bordersize);
}
然后,您需要在两个方向上设置内容压缩阻力优先级:
[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintHorizontal];
[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintVertical];
我找到的一个解决方案是重载addConstraint:
方法并在添加之前修改约束:
- (void)addConstraint:(NSLayoutConstraint *)constraint
{
if(constraint.firstItem == self || constraint.secondItem == self) {
if(constraint.firstAttribute == NSLayoutAttributeLeading) {
constraint.constant += self.leftBorderWidth;
} else if (constraint.firstAttribute == NSLayoutAttributeTrailing) {
constraint.constant += self.rightBorderWidth;
} else if (constraint.firstAttribute == NSLayoutAttributeTop) {
constraint.constant += self.topBorderWidth;
} else if (constraint.firstAttribute == NSLayoutAttributeBottom) {
constraint.constant += self.bottomBorderWidth;
}
}
[super addConstraint:constraint];
}
然后还在xxxBorderWidth
setter中处理这些约束。
为了将来参考,您可以覆盖NSView.alignmentRectInsets
以影响布局指南的位置:
在其内容周围绘制装饰的自定义视图可以覆盖此属性,并返回与内容边缘对齐的插图,不包括装饰。这允许基于约束的布局系统基于其内容而不仅仅是其框架来对齐视图。
链接到文档:
https://developer.apple.com/documentation/appkit/nsview/1526870-alignmentrectinsets