使用Cocoa自动布局的自定义视图中的内容填充

时间:2013-04-06 11:47:15

标签: objective-c macos cocoa nsview autolayout

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

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

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

布局必须是:

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

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

4 个答案:

答案 0 :(得分:2)

更新

我刚注意到你的问题是NSView(OS X),而不是UIView(iOS)。好吧,这个想法应该仍然适用,但你不能将我的代码放到你的项目中不变。遗憾。

ORIGINAL

考虑更改视图层次结构。假设您的自定义边框视图称为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的{​​{1}}插座。

要以这种方式实现contentView,我们需要为BorderView及其BorderView之间的四个约束中的每一个设置一个实例变量:

contentView

@implementation BorderView { NSLayoutConstraint *topConstraint; NSLayoutConstraint *leftConstraint; NSLayoutConstraint *bottomConstraint; NSLayoutConstraint *rightConstraint; UIView *_contentView; } 访问者可以按需创建内容视图:

contentView

如果有的话,setter可以替换现有的内容视图:

#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]; } setter需要安排更新约束并重新绘制边框:

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;
}

我在this git repository中提供了一个完整的工作示例。

答案 1 :(得分: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 setters中处理这些约束。

答案 2 :(得分:0)

您是否尝试过设置内在尺寸以包含边框尺寸?

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

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

[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintHorizontal];
[self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintVertical];

答案 3 :(得分:0)

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

  可以覆盖

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

文档链接:

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