我在 IB 中设计了我的自定义单元,将其子类化并将我的插座连接到我的自定义类。我在单元格内容中有三个子视图,它们是:UIView (cdView) 和两个标签(titleLabel 和 emailLabel)。根据每行可用的数据,有时我希望在单元格中显示 UIView 和两个标签,有时只显示两个标签。如果我将 UIView 属性设置为隐藏,或者我将从超级视图中删除它,那么我想要做的是设置约束,这两个标签将向左移动。我尝试将 UIView 前导约束设置为 Superview(单元格内容)为 10px 和 UILabels 前导约束为 10 px 到下一个视图(UIView)。稍后在我的代码中
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
// ...
Record *record = [self.records objectAtIndex:indexPath.row];
if ([record.imageURL is equalToString:@""]) {
cell.cdView.hidden = YES;
}
}
我隐藏了我的 cell.cdView,我希望标签向左移动,但它们在 Cell 中的位置相同。我试图从 superview 中删除 cell.cdView 但它也不起作用。我附上了图片以澄清我的意思。
https://s21.postimg.cc/m7c5bss4n/cells.png
我知道如何以编程方式做到这一点,我不是在寻找那个解决方案。我想要的是在 IB 中设置约束,我希望如果其他视图被删除或隐藏,我的子视图将动态移动。是否可以通过自动布局在 IB 中执行此操作?
.....
这是可能的,但你必须做一些额外的工作。首先有一些概念性的事情需要解决:
隐藏视图,即使它们不绘制,仍然参与自动布局并通常保留它们的框架,而将其他相关视图留在它们的位置。
当从其父视图中删除视图时,所有相关的约束也会从该视图层次结构中删除。
在您的情况下,这可能意味着:
如果您将左视图设置为隐藏,则标签将保留在原位,因为该左视图仍在占用空间(即使它不可见)。
如果您删除左视图,您的标签可能会受到模棱两可的约束,因为您不再有标签左边缘的约束。
你需要做的是明智地过度限制你的标签。不理会现有的约束(另一个视图的 10pts 空间),但添加另一个约束:使标签的左边缘距其父视图的左边缘 10pts,并具有非必需的优先级(默认的高优先级可能会很好用)。
然后,当您希望它们向左移动时,完全删除左视图。对左视图的强制 10pt 约束将与它相关的视图一起消失,并且您将只剩下一个高优先级约束,即标签距离其父视图 10pts。在下一个布局过程中,这应该会导致它们向左扩展,直到它们填满超级视图的宽度,但要在边缘周围留出间距。
一个重要的警告:如果你想要你的左视图回到图片中,你不仅必须将它添加回视图层次结构,而且你还必须同时重新建立它的所有约束。这意味着当视图再次显示时,您需要一种方法将视图及其标签之间的 10pt 间距约束放回原处。
在运行时添加或删除约束是一项可能影响性能的重量级操作。但是,还有一个更简单的替代方案。
对于要隐藏的视图,设置宽度约束。用与该视图的前导水平间隙来约束其他视图。
要隐藏,请将宽度约束的 .constant
更新为 0.f。其他视图将自动向左移动以占据位置。
有关更多详细信息,请参阅我的其他答案:
How to change label constraints during runtime?
NSLayoutConstraint
的常量属于 CGFloat
类型,它是 double
的 typedef(Apple Watch 除外,它是 float
)。因此,为了避免混乱的演员表,最好将宽度约束设置为 0.0
而不是 0.f
。
对于仅支持 iOS 8+ 的用户,有一个新的布尔属性 active。这将有助于动态启用仅需要的约束
PS 约束出口一定要强,不能弱 例子:
@IBOutlet weak var optionalView: UIView!
@IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
@IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!
func showView() {
optionalView.isHidden = false
viewIsVisibleConstraint.isActive = true
viewIsHiddenConstraint.isActive = false
}
func hideView() {
optionalView.isHidden = true
viewIsVisibleConstraint.isActive = false
viewIsHiddenConstraint.isActive = true
}
此外,要修复情节提要中的错误,您需要取消选中其中一个约束的 Installed
复选框。
UIStackView (iOS 9+)
另一种选择是将您的视图包装在 UIStackView
中。一旦视图被隐藏 UIStackView
将自动更新布局
当 hidden
属性在其任何子视图上发生更改时,UIStackView
会自动重新定位其视图(iOS 9+)。
UIView.animateWithDuration(1.0) { () -> Void in
self.mySubview.hidden = !self.mySubview.hidden
}
跳转到此 WWDC 视频中的 11:48 观看演示:
Mysteries of Auto Layout, Part 1
我的项目使用 UILabel
的自定义 @IBDesignable
子类(以确保颜色、字体、插图等的一致性),并且我实现了如下内容:
override func intrinsicContentSize() -> CGSize {
if hidden {
return CGSizeZero
} else {
return super.intrinsicContentSize()
}
}
这允许标签子类参与自动布局,但隐藏时不占用空间。
对于 Google 员工:基于 Max 的回答,为了解决许多人注意到的填充问题,我只是增加了标签的高度,并使用该高度作为分隔符而不是实际的填充。这个想法可以扩展到任何包含视图的场景。
这是一个简单的例子:
https://i.stack.imgur.com/RGE8v.png
在这种情况下,我将 Author 标签的高度映射到适当的 IBOutlet
:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
当我将约束的高度设置为 0.0f
时,我们会保留“填充”,因为 Play 按钮的高度允许这样做。
将uiview和标签之间的约束连接为IBOutlet,并在设置hidden = YES时将优先级成员设置为较小的值
我最终做的是创建 2 个 xib。一个有左视图,一个没有左视图。我在控制器中注册了两者,然后决定在 cellForRowAtIndexPath 期间使用哪个。
他们使用相同的 UITableViewCell 类。缺点是 xib 之间的内容有一些重复,但这些单元格非常基本。好处是我没有一堆代码来手动管理删除视图、更新约束等。
一般来说,这可能是一个更好的解决方案,因为它们在技术上是不同的布局,因此应该有不同的 xib。
[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];
TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];
在这种情况下,我将 Author 标签的高度映射到适当的 IBOutlet:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
当我将约束的高度设置为 0.0f 时,我们保留“填充”,因为播放按钮的高度允许它。
cell.authorLabelHeight.constant = 0;
https://i.stack.imgur.com/REUDc.png
https://i.stack.imgur.com/BBuX9.png
只需使用 UIStackView ,一切都会正常工作。无需担心其他约束,UIStackView 会自动处理空间。
对于这个特定的布局,要使用的约束是隐藏视图上的“领先”约束。不过,下面的理论将在各个方向起作用。
1:设置所有约束在所有视图可见时您希望它的外观。
2:向要隐藏的视图添加第二个“前导”约束。这将暂时打破约束。
3:将原始前导约束的优先级更改为 '999' - 然后将优先级设置为 1000 的新约束,并且不会再破坏任何约束。
4:将新约束从“leading=leading”更改为“trailing=leading”。这会将您想要隐藏的视图从其父级的前沿移开。
5:切换新约束的 isActive 值现在将切换它是否在视图中或视图之外。在将可见性设置为真/假的同时将其设置为真/假。例如:
@IBOutlet var avatar:UIImage!
@IBOutlet var avatarLeadHid:NSLayoutConstraint!
func hideAvatar() {
self.avatar.isHidden = true
self.avatarLeadHid.isActive = true
}
func showAvatar() {
self.avatar.isHidden = false
self.avatarLeadHid.isActive = false
}
奖励:您可以调整新隐藏约束的“常量”值,以更改隐藏视图时使用的填充/边距。这个值可以是负数。
额外奖励:只需切换隐藏约束上的“已安装”复选框,就可以从 Interface Builder 中查看布局的外观,而无需运行任何代码。
进一步的帮助:我制作了一个视频,展示了我比要点列表做得更好:https://youtu.be/3tGEwqtQ-iU
在我的例子中,我将 高度约束 的常量设置为 0.0f
,并将 hidden
属性设置为 YES
。
为了再次显示视图(带有子视图),我做了相反的事情:我将高度常数设置为非零值并将 hidden
属性设置为 NO
。
试试这个,我已经实现了下面的代码,
我在 ViewController 上有一个视图,其中添加了其他三个视图,当隐藏任何视图时,其他两个视图将移动,请按照以下步骤操作。 ,
1.ViewController.h文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *viewOne;
@property (strong, nonatomic) IBOutlet UIView *viewTwo;
@property (strong, nonatomic) IBOutlet UIView *viewThree;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
@end
2.ViewController.m
#import "ViewController.h"
@interface ViewController ()
{
CGFloat viewOneWidthConstant;
CGFloat viewTwoWidthConstant;
CGFloat viewThreeWidthConstant;
CGFloat viewBottomWidthConstant;
}
@end
@implementation ViewController
@synthesize viewOne, viewTwo, viewThree;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a
nib.
/*
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
*/
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
[self hideShowBottomBar];
}
- (void)hideShowBottomBar
{
BOOL isOne = !viewOne.isHidden;
BOOL isTwo = !viewTwo.isHidden;
BOOL isThree = !viewThree.isHidden;
viewOneWidthConstant = _viewOneWidth.constant;
viewTwoWidthConstant = _viewTwoWidth.constant;
viewThreeWidthConstant = _viewThreeWidth.constant;
viewBottomWidthConstant = _viewBottomWidth.constant;
if (isOne && isTwo && isThree) {
// 0 0 0
_viewOneWidth.constant = viewBottomWidthConstant / 3;
_viewTwoWidth.constant = viewBottomWidthConstant / 3;
_viewThreeWidth.constant = viewBottomWidthConstant / 3;
}
else if (isOne && isTwo && !isThree) {
// 0 0 1
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = 0;
}
else if (isOne && !isTwo && isThree) {
// 0 1 0
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (isOne && !isTwo && !isThree) {
// 0 1 1
_viewOneWidth.constant = viewBottomWidthConstant;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
else if (!isOne && isTwo && isThree) {
// 1 0 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (!isOne && isTwo && !isThree) {
// 1 0 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant;
_viewThreeWidth.constant = 0;
}
else if (!isOne && !isTwo && isThree) {
// 1 1 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant;
}
else if (isOne && isTwo && isThree) {
// 1 1 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
https://i.stack.imgur.com/S4tJv.png
希望所以这个逻辑会帮助一些人。
我将使用水平堆栈视图。它可以在子视图隐藏时移除框架。
在下图中,红色视图是您的内容的实际容器,并且与橙色超级视图 (ShowHideView) 有 10pt 的尾随空间,然后只需将 ShowHideView 连接到 IBOutlet 并以编程方式显示/隐藏/删除它。
这是视图可见/已安装的时间。
https://i.stack.imgur.com/kEuZz.png
这是视图被隐藏/未安装的时候。
https://i.stack.imgur.com/GfG4Z.png
这是我使用优先级约束的另一个解决方案。这个想法是将宽度设置为0。
创建容器视图(橙色)并设置宽度。创建内容视图(红色)并将尾随空间 10pt 设置为超级视图(橙色)。注意尾随空间约束,有 2 个具有不同优先级的尾随约束。低(=10)和高(<=10)。这对于避免歧义很重要。将橙色视图的宽度设置为 0 以隐藏视图。
最简单的解决方案是使用 UIStackView(水平)。添加到堆栈视图:第一个视图和带有标签的第二个视图。然后将第一个视图的 isHidden 属性设置为 false。所有约束都将被计算并自动更新。
当您想要隐藏 UIView 时,创建宽度约束并将其更改为 0,而不是隐藏视图。
这可能是最简单的方法。此外,它将保留视图,如果您想再次显示它,则无需重新创建它(非常适合在表格单元格内使用)。要更改常量值,您需要创建一个常量参考插座(与您为视图创建插座的方式相同)。
正如 no_scene 建议的那样,您绝对可以通过在运行时更改约束的优先级来做到这一点。这对我来说要容易得多,因为我有多个必须删除的阻塞视图。
这是一个使用 ReactiveCocoa 的片段:
RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;
RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
combineLatest:@[isViewOneHiddenSignal,
isViewTwoHiddenSignal,
isViewThreeHiddenSignal]]
and]
distinctUntilChanged]
map:^id(NSNumber* allAreHidden) {
return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
}];
RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
subscribeNext:^(id x) {
@strongify(self);
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}];
如果这对某人有帮助,我构建了一个帮助类来使用 visual format 约束。我在我当前的应用程序中使用它。
它可能有点适合我的需要,但您可能会发现它很有用,或者您可能想要修改它并创建自己的助手。
我必须感谢蒂姆的answer above、这个answer about UIScrollView 和这个tutorial。
以下是我将如何重新调整我的 uiviews 以获得您的解决方案:
拖放一个 UIImageView 并将其放在左侧。拖拽一个 UIView 放到 UIImageView 的右边。将两个 UILabel 拖放到该 UIView 中,其前导和尾随约束为零。将包含 2 个标签的 UIView 的前导约束设置为 superview 而不是 UIImagView。如果 UIImageView 被隐藏,则将前导约束常量设置为 10 px 以进行超级视图。否则,将前导约束常量设置为 10 px + UIImageView.width + 10 px。
我创建了自己的拇指规则。每当您必须隐藏/显示其约束可能受到影响的任何 uiview 时,请在 uiview 中添加所有受影响/依赖的子视图,并以编程方式更新其前导/尾随/顶部/底部约束常量。
这是一个老问题,但我仍然希望它会有所帮助。来自 Android,在这个平台上,您有一个方便的方法 isVisible
可以将其从视图中隐藏,但在自动布局绘制视图时也不会考虑框架。
使用扩展和“扩展”uiview,您可以在 ios 中执行类似的功能(不知道为什么它已经不在 UIKit 中)这里是 swift 3 中的实现:
func isVisible(_ isVisible: Bool) {
self.isHidden = !isVisible
self.translatesAutoresizingMaskIntoConstraints = isVisible
if isVisible { //if visible we remove the hight constraint
if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
self.removeConstraint(constraint)
}
} else { //if not visible we add a constraint to force the view to have a hight set to 0
let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
self.addConstraint(height)
}
self.layoutIfNeeded()
}
正确的方法是使用 isActive = false 禁用约束。但是请注意,停用约束会删除并释放它,因此您必须为它们提供强大的出口。
我认为这是最简单的答案。请验证它是否有效:
StackFullView.layer.isHidden = true
Task_TopSpaceSections.constant = 0. //your constraint of top view
在这里查看https://www.youtube.com/watch?v=EBulMWMoFuw
不定期副业成功案例分享
|-(space)-[hidden(0)]-(space)-[visible]
实际上是|-(2*space)-[visible]
。其次,该视图可能会根据其自己的视图子树和约束开始引发约束违规——您不能保证可以任意将视图约束为 0 宽度并使其继续工作。|-[otherViews]-[eitherThis][orThis]-|
),但我最终会遇到这个问题。