我希望文本标签根据绿色视图调整其位置。 如果可能的话,它应该与视图左对齐,但如果文本内容超出仪表板,我希望文本与视图右对齐。示例:
绿色视图的位置随机变化,我不想每次都计算文本框架并手动启用或禁用不同的约束,或者直接设置框架。我真的很想探索一些简单的方法来实现这一目标
目前我使用:
C1 = [text.leadingAnchor constraintEqualToAnchor:view.leadingAnchor]
C2 = [text.trailingAnchor constraintLessThanOrEqualToAnchor:view.trailingAnchor]
C3 = [text.trailingAnchor constraintLessThanOrEqualToAnchor:dashboard.trailingAnchor]
优先级: C3>C2>C1
但在这种情况下,中间绿色视图的文本位置是错误的。
您的问题实际上是,如果适合的话是否可以将其与绿色视图右对齐,否则左对齐。没有一组简单的约束可以自动做到这一点。理论上,您可以有两个约束,一个用于左对齐,另一个用于右对齐,然后根据绿色视图在其超级视图中的位置以编程方式激活一个约束并停用另一个约束。 (或者,不用说,您可以完全采用老式方法,手动调整框架。)
但是有一个简单的替代方案可以将其完全保持在约束系统的范围内,无需任何手动调整,但这并不完全是您所要求的。但是,这可能足以满足您的目的:具体来说,如果有空间,您可以将其与绿色视图右对齐,如果没有空间,则可以将其与绿色视图的 superview 左对齐。
基本思想是对容器(仪表板视图)的标签有必要的约束,但对绿色视图的后缘有较低优先级的约束。
您可能还想设置标签的内容拥抱和内容抗压缩性的优先级。这些优先级应该高于绿色视图的尾锚约束。例如:
let c1 = label.leadingAnchor.constraint(greaterThanOrEqualTo: dashboard.leadingAnchor)
let c2 = label.trailingAnchor.constraint(equalTo: greenView.trailingAnchor)
let c3 = label.trailingAnchor.constraint(lessThanOrEqualTo: dashboard.trailingAnchor)
c2.priority = .defaultLow
label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label.setContentHuggingPriority(.required, for: .horizontal)
NSLayoutConstraint.activate([c1, c2, c3])
产量:
或者,在 Objective-C 中:
NSLayoutConstraint *c1 = [label.leadingAnchor constraintGreaterThanOrEqualToAnchor:dashboard.leadingAnchor];
NSLayoutConstraint *c2 = [label.trailingAnchor constraintEqualToAnchor:greenView.trailingAnchor];
NSLayoutConstraint *c3 = [label.trailingAnchor constraintLessThanOrEqualToAnchor:dashboard.trailingAnchor];
c2.priority = UILayoutPriorityDefaultLow;
[label setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[NSLayoutConstraint activateConstraints:@[c1, c2, c3]];
或者,不用说,您可以在 Interface Builder 中设置所有这些约束和优先级,根本不需要任何代码。但希望这说明了如何使用优先级来达到预期的效果。
如前所述,这不能仅通过约束来完成,但逻辑相当简单,我们可以利用约束来完成大多数的工作。
所以,目标:
显然,如果字符串比视图宽度短,我们不需要做任何事情。
当标签比视图宽时:
.active
属性但是——如果标签在右对齐时太宽而无法容纳,会发生什么?
我们可以将前导/尾随约束的优先级从标签降低到“绿色视图”,并在标签和超级视图之间添加 required 约束:
这可能最终看起来有点“不平衡”——所以我们可以添加另一段逻辑......我们将使用“绿色视图”的左/右更宽的“间隙”和superview优先布局:
这是一些示例代码...
AttachedLabelView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AttachedLabelView : UIView
@property (strong, nonatomic) UILabel *attachedLabel;
@end
NS_ASSUME_NONNULL_END
AttachedLabelView.m
#import "AttachedLabelView.h"
@interface AttachedLabelView ()
{
UILabel *myLabel;
NSLayoutConstraint *labelLeading;
NSLayoutConstraint *labelTrailing;
NSLayoutConstraint *limitLabelLeading;
NSLayoutConstraint *limitLabelTrailing;
}
@end
@implementation AttachedLabelView
- (void)setAttachedLabel:(UILabel *)attachedLabel {
// make sure this is set
attachedLabel.translatesAutoresizingMaskIntoConstraints = NO;
// initialize leading and trailing constraints
// these will be toggled so the label is either
// aligned with the leading edge, or
// aligned with the trailing edge
labelLeading = [attachedLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor];
labelTrailing = [attachedLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor];
labelLeading.priority = UILayoutPriorityDefaultHigh;
labelTrailing.priority = UILayoutPriorityDefaultHigh;
// label will always be "pinned" to the bottom of self
[attachedLabel.topAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
myLabel = attachedLabel;
[self setNeedsLayout];
}
- (UILabel *)attachedLabel {
return myLabel;
}
- (void)layoutSubviews {
[super layoutSubviews];
UIView *sv = self.superview;
UILabel *myLabel = self.attachedLabel;
// don't try to do anything if we don't have
// a superview AND an "attached" label
if (!sv || !myLabel) {
return;
}
// if not yet set, do so now
// these constraints prevent the label from extending outside the superview frame
if (!limitLabelLeading) {
limitLabelLeading = [myLabel.leadingAnchor constraintGreaterThanOrEqualToAnchor:sv.leadingAnchor];
limitLabelLeading.active = YES;
limitLabelTrailing = [myLabel.trailingAnchor constraintLessThanOrEqualToAnchor:sv.trailingAnchor];
limitLabelTrailing.active = YES;
}
// disable both constraints
labelLeading.active = NO;
labelTrailing.active = NO;
// get self's "left" and "right"
CGFloat myMinX = CGRectGetMinX(self.frame);
CGFloat myMaxX = CGRectGetMaxX(self.frame);
// get the widths of the
// leading "empty space" and
// trailing "empty space"
CGFloat leadingGap = myMinX;
CGFloat trailingGap = sv.frame.size.width - myMaxX;
// the logic:
// IF the label is too long to fit in the superview when "left-aligned"
// AND
// IF there is more space on "my left" than on "my right"
// then we use the Trailing constraint
if (myMinX + myLabel.intrinsicContentSize.width > sv.frame.size.width) {
if (leadingGap > trailingGap) {
labelTrailing.active = YES;
}
}
labelLeading.active = !labelTrailing.active;
}
@end
SimpleExampleViewController.h - 显示用法的简单示例...
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface SimpleExampleViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
SimpleExampleViewController.m
#import "SimpleExampleViewController.h"
#import "AttachedLabelView.h"
@interface SimpleExampleViewController ()
{
UIView *dashboardView;
AttachedLabelView *exampleView;
UILabel *someLabel;
}
@end
@implementation SimpleExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
// change the string and the x-position to see the differences
NSString *sample = @"This string is a little longer.";
CGFloat xPos = 40.0;
self.view.backgroundColor = UIColor.systemBackgroundColor;
dashboardView = [UIView new];
dashboardView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
exampleView = [AttachedLabelView new];
exampleView.backgroundColor = UIColor.systemGreenColor;
someLabel = [UILabel new];
someLabel.backgroundColor = UIColor.cyanColor;
dashboardView.translatesAutoresizingMaskIntoConstraints = NO;
exampleView.translatesAutoresizingMaskIntoConstraints = NO;
someLabel.translatesAutoresizingMaskIntoConstraints = NO;
// add the custom view and label to the dashboardView
[dashboardView addSubview:exampleView];
[dashboardView addSubview:someLabel];
// add dashboardView
[self.view addSubview:dashboardView];
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:@[
[dashboardView.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[dashboardView.heightAnchor constraintEqualToConstant:140.0],
[dashboardView.widthAnchor constraintEqualToConstant:340.0],
[dashboardView.centerXAnchor constraintEqualToAnchor:g.centerXAnchor],
[exampleView.widthAnchor constraintEqualToConstant:120.0],
[exampleView.heightAnchor constraintEqualToConstant:80.0],
[exampleView.topAnchor constraintEqualToAnchor:dashboardView.topAnchor constant:20.0],
[exampleView.leadingAnchor constraintEqualToAnchor:dashboardView.leadingAnchor constant:xPos],
]];
someLabel.text = sample;
// "attach" the label to exampleView
[exampleView setAttachedLabel:someLabel];
}
@end
AttachExampleViewController.h - 一个更复杂的示例,通过示例标签显示“可拖动”绿色视图和“点击循环”的用法...
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AttachExampleViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
附加ExampleViewController.m
#import "AttachExampleViewController.h"
#import "AttachedLabelView.h"
@interface AttachExampleViewController ()
{
UIView *dashboardView;
AttachedLabelView *exampleView;
UILabel *someLabel;
NSMutableArray <NSString *>*samples;
NSLayoutConstraint *evTopConstraint;
NSLayoutConstraint *evLeadingConstraint;
}
@end
@implementation AttachExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
// some sample strings to cycle through
samples = [NSMutableArray arrayWithObjects:
@"Sample string.",
@"This string is a little longer.",
@"Might extend outside the superview.",
nil
];
self.view.backgroundColor = UIColor.systemBackgroundColor;
dashboardView = [UIView new];
dashboardView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
exampleView = [AttachedLabelView new];
exampleView.backgroundColor = UIColor.systemGreenColor;
someLabel = [UILabel new];
someLabel.backgroundColor = UIColor.cyanColor;
dashboardView.translatesAutoresizingMaskIntoConstraints = NO;
exampleView.translatesAutoresizingMaskIntoConstraints = NO;
someLabel.translatesAutoresizingMaskIntoConstraints = NO;
// add the custom view and label to the dashboardView
[dashboardView addSubview:exampleView];
[dashboardView addSubview:someLabel];
// add dashboardView
[self.view addSubview:dashboardView];
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:@[
[dashboardView.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[dashboardView.heightAnchor constraintEqualToConstant:140.0],
[dashboardView.widthAnchor constraintEqualToConstant:340.0],
[dashboardView.centerXAnchor constraintEqualToAnchor:g.centerXAnchor],
]];
// add a "prompt" label
UILabel *pLabel = [UILabel new];
pLabel.numberOfLines = 0;
pLabel.textAlignment = NSTextAlignmentCenter;
pLabel.text = @"Tap outside the gray \"dashboard\" view\nto cycle through the sample strings.";
pLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:pLabel];
[NSLayoutConstraint activateConstraints:@[
[pLabel.topAnchor constraintEqualToAnchor:dashboardView.bottomAnchor constant:20.0],
[pLabel.leadingAnchor constraintEqualToAnchor:dashboardView.leadingAnchor],
[pLabel.trailingAnchor constraintEqualToAnchor:dashboardView.trailingAnchor],
]];
// start with exampleView at 20,40
evTopConstraint = [exampleView.topAnchor constraintEqualToAnchor:dashboardView.topAnchor constant:20.0];
evLeadingConstraint = [exampleView.leadingAnchor constraintEqualToAnchor:dashboardView.leadingAnchor constant:40.0];
// this avoids auto-layout console warnings
evTopConstraint.priority = UILayoutPriorityRequired - 1;
evLeadingConstraint.priority = UILayoutPriorityRequired - 1;
[NSLayoutConstraint activateConstraints:@[
evTopConstraint, evLeadingConstraint,
[exampleView.widthAnchor constraintEqualToConstant:120.0],
[exampleView.heightAnchor constraintEqualToConstant:80.0],
]];
// we're making exampleView draggable
UIPanGestureRecognizer *p = [UIPanGestureRecognizer new];
[p addTarget:self action:@selector(handlePan:)];
[exampleView addGestureRecognizer:p];
// "attach" the label to exampleView
[exampleView setAttachedLabel:someLabel];
// update the label text
[self nextString];
}
- (void)handlePan:(UIPanGestureRecognizer *)p {
UIView *v = p.view;
UIView *sv = v.superview;
CGPoint pt = [p translationInView:v];
[p setTranslation:CGPointZero inView:v];
// let's prevent dragging the exampleView outside of the dashboardView frame
CGFloat xMax = sv.frame.size.width - v.frame.size.width;
CGFloat yMax = sv.frame.size.height - (v.frame.size.height + someLabel.frame.size.height);
CGFloat x = evLeadingConstraint.constant + pt.x;
CGFloat y = evTopConstraint.constant + pt.y;
evLeadingConstraint.constant = MIN(xMax, MAX(x, 0.0));
evTopConstraint.constant = MIN(yMax, MAX(y, 0.0));
// tell exampleView to update the label position (if necessary)
[exampleView setNeedsLayout];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self.view];
if (CGRectContainsPoint(dashboardView.frame, p)) {
return;
}
// if we tap outside of dashboardView, cycle to the next sample string
[self nextString];
}
- (void)nextString {
NSString *s = [samples firstObject];
[samples removeObject:s];
[samples addObject:s];
someLabel.text = s;
// tell exampleView to update the label position (if necessary)
[exampleView setNeedsLayout];
}
@end