0

我有一个自定义NSView子类,它本身有一个边框。在此视图内绘制边框。是否可以通过自动布局来尊重这个边界?

例如,当我将子视图放置到我的自定义视图并设置如下约束时:

@"H:|-(myViewSubView)-|" (not @"H:|-(myViewBorderWidth)-(myViewSubView)-(myViewBorderWidth)-|")
@"V:|-(myViewSubView)-|"

布局必须是:

Horizontal: |-(myViewBorderWidth)-|myViewSubview|-(myViewBorderWidth)-|
  Vertical: |-(myViewBorderWidth)-|myViewSubview|-(myViewBorderWidth)-| 

我试图-bounds在我的视图中覆盖方法以返回没有边界的边界矩形,但这没有帮助。

4

4 回答 4

2

更新

我刚刚注意到您的问题是在谈论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一个出口。这样,我们可以创建一个单独的顶级视图用作内容视图,并将其连接到BorderViewcontentView插座。

为了实现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];
}

borderSizesetter 需要安排更新约束和重绘边框:

- (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 存储库中放了一个完整的工作示例。

于 2013-04-06T18:25:40.313 回答
1

为了将来参考,您可以覆盖NSView.alignmentRectInsets以影响布局指南的位置:

在其内容周围绘制装饰的自定义视图可以覆盖此属性并返回与内容边缘对齐的插图,不包括装饰。这允许基于约束的布局系统根据它们的内容对齐视图,而不仅仅是它们的框架。

文档链接:

https://developer.apple.com/documentation/appkit/nsview/1526870-alignmentrectinsets

于 2018-09-11T13:22:22.010 回答
0

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

于 2013-04-06T15:14:37.353 回答
0

您是否尝试过将内在尺寸设置为包括边框尺寸?

- (NSSize)intrinsicContentSize
{
    return NSMakeSize(width+bordersize, height+bordersize);
}

那么您需要在两个方向上设置内容压缩阻力优先级:

[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintHorizontal];
[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintVertical];
于 2013-04-06T16:03:54.183 回答