我有一个自定义的NSView子类,该子类周围有边框。在此视图内部绘制边框。有可能遵守自动布局的边界吗?

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

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


布局必须为:

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


我尝试覆盖视图中的-bounds方法以返回无边界的矩形rect,但这无济于事。

最佳答案

更新

我只是注意到您的问题是在谈论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,我们将需要一个实例变量,用于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;
}


如果有一个设置器,则可以替换现有的内容视图:

- (void)setContentView:(UIView *)contentView {
    if (_contentView) {
        [self destroyContentView];
    }
    _contentView = contentView;
    [self addSubview:contentView];
}


borderSize设置器需要安排要更新的约束并重新绘制边框:

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

08-16 05:48