在 UIScrollView 子视图中使用自动布局(以编程方式)
Posted
技术标签:
【中文标题】在 UIScrollView 子视图中使用自动布局(以编程方式)【英文标题】:Using autolayout in UIScrollView subviews (programmatically) 【发布时间】:2014-10-22 09:25:07 【问题描述】:首先,我一直在阅读与此类似的所有问题,但没有成功,所以最后我会尝试询问我的具体情况。
我有一个 UIScrollView,我以这种方式以编程方式填充组件:
- (void) fillScrollView(int numItems)
float posY = 10.0;
for(int i = 0; i < numItems; i++)
UIImageView *leftImg = [[UIImageView alloc] init];
[leftImg setTranslatesAutoresizingMaskIntoConstraints:NO];
[leftImg setFrame:CGRectMake(10.0, posY, 20.0, 20.0)];
[leftImg setImage:[UIImage imageNamed:@"img1"]];
UILabel *txtLb = [[UILabel alloc] init];
[txtLb setTranslatesAutoresizingMaskIntoConstraints:NO];
[txtLb setFont:[UIFont systemFontOfSize:15.0]];
[txtLb setNumberOfLines:0];
[txtLb setLineBreakMode:NSLineBreakByWordWrapping];
[txtLb setFrame:CGRectMake(40.0, posY, 240.0, 20.0)];
NSString *data = @"This is my example text, it could be longer.";
[txtLb setText:data];
CGRect paragraphRect = [dato boundingRectWithSize:CGSizeMake(txtLb.frame.size.width, 9999.0)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:@NSFontAttributeName: txtLb.font
context:nil];
float height = paragraphRect.size.height;
CGRect frame = txtLb.frame;
frame.size.height = ceil(height);
[txtLb setFrame:frame];
UIImageView *rightImg = [[UIImageView alloc] init];
[rightImg setTranslatesAutoresizingMaskIntoConstraints:NO];
[rightImg setFrame:CGRectMake(290.0, posY, 20.0, 20.0)];
[rightImg setImage:[UIImage imageNamed:@"img2"]];
[_scrollV addSubview:leftImg];
[_scrollV addSubview:txtLb];
[_scrollV addSubview:rightImg];
height = height + 20.0;
if(height < 40.0) height = 40.0;
posY = posY + height;
[_scrollV setContentSize:CGSizeMake(_scrollV.frame.size.width, posY)];
我希望这些约束是:
H:|-10-[leftImg(20)]-10-[txtLb]-10-[rightImg(20)]-10-| 并且在垂直方向上,每一行与上一行垂直间隔为 10px。
我尝试过使用 constraintWithItem,但我对在这种情况下如何使用它感到困惑。
任何帮助将不胜感激。亲切的问候!
编辑
按照张的建议,我将 3 个组件放在 UIView 中。这样我就可以毫无问题地在它们之间使用自动布局,并且每个 UIView 中的一切都在正确的位置。
但是,在循环内使用 UIView 之间的自动布局时,我仍然遇到问题。我正在这样做:
UIScrollView 所有块都没有左/右边距。
// 0px to the left of the UIScrollView
NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeLeftMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeRightMargin
multiplier:1.0
constant:0.0];
// 0px to the right of the UIScrollView
NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeRightMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeLeftMargin
multiplier:1.0
constant:0.0];
[_scrollV addConstraint:constraintLeft];
[_scrollV addConstraint:constraintRight];
关于block之间的垂直分隔,当UIView是UIScrollView的第一个block时:
// 10px below UIScrollView top
NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeTopMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0
constant:10.0];
[_scrollV addConstraint:constraintTop];
当 UIView 上面有任何块时:
// 10px below previous block
NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeTopMargin
relatedBy:NSLayoutRelationEqual
toItem:previousBlock
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0
constant:10.0];
[_scrollV addConstraint:constraintTop];
这显示了所有没有垂直分隔的块,都在相同的 Y 位置,并且还给出了应用约束的错误。 我确定我没有使用正确的方法 constraintWithItem,但我找不到这种用法的示例。
【问题讨论】:
我建议您将这些子视图放在 for 循环中并将它们放入自定义 UIView 类中,在该类中正确设置自动布局,然后只需要在 for 循环中管理单个自定义视图。 谢谢张!但也许我遗漏了一些东西:如果我将所有这些子视图放入一个唯一的 UIView(以及 UIScrollView 中的那个 UIView),我会遇到与子视图的自动布局相同的问题,不是吗?由于它们是动态的,并且直到运行时我才知道它们的数量,所以我也必须在运行时设置自动布局,不是吗? 抱歉再次发表评论。我已经重读了你的评论,我想我现在理解你了。您不是要我将所有组件放在一个大的 UIView 中,而是将每个块(每个循环)放在 UIView 中,这样我可以更轻松地设置块之间的垂直间距。我会试试这个。 检查我的新答案,我已经给了你两个解决方案,一个使用你的 UIScrollView 并手动添加行 vs UICollectionView。 我刚刚阅读了您的新答案,我会尝试并让您知道。非常感谢!! 【参考方案1】:您似乎正在尝试重新发明***伴侣。您可能应该使用 UICollectionView 或 UITableView 而不是 UIScrollView 并手动添加您的单元格。
无论如何,对于你的 scrollView 方法,你可以实现它的一种方式是这样的:
ViewController 头文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@end
ViewController 实现文件
#import "ViewController.h"
#import "Cell.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
[self fillScrollView:15];
- (void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
-(BOOL)prefersStatusBarHidden
return YES;
-(void)initViews
self.scrollView = [[UIScrollView alloc] init];
// ------------------------------------------------------------------
// This content view will be the only child view of scrollview
// ------------------------------------------------------------------
self.contentView = [[UIView alloc] init];
// add content view to scrollview now
[self.scrollView addSubview:self.contentView];
// add scrollview to main view
[self.view addSubview:self.scrollView];
-(void)initConstraints
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
id views = @
@"scrollView": self.scrollView,
@"contentView": self.contentView
;
// setup scrollview constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:nil views:views]];
// ---------------------------------------
// setup content view constraint
//
// note: need to pin all four side of
// contentView to scrollView
// ---------------------------------------
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:nil views:views]];
-(void)fillScrollView:(int) numItems
// clear any previous cells before adding new ones
[self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
// Need to construct the layout visual format string
NSMutableString *strVerticalConstraint = [[NSMutableString alloc] init];
[strVerticalConstraint appendString:@"V:|"];
// this dictionary will hold all the key-value pair that identifies all the subviews
NSMutableDictionary *subviews = [[NSMutableDictionary alloc] init];
for(int i = 0; i < numItems; i++)
Cell *cell = [[Cell alloc] init];
// customize the cell's appearance here
cell.leftImage.image = [UIImage imageNamed:@"leftImage.png"];
cell.textLabel.text = @"This is my example text, it could be longer.";
cell.rightImage.image = [UIImage imageNamed:@"rightImage.png"];
[self.contentView addSubview:cell];
cell.translatesAutoresizingMaskIntoConstraints = NO;
id views = @
@"cell": cell
;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cell]|" options:0 metrics:nil views:views]];
// prevent cell's width to extend beyond screen width
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:self.view.bounds.size.width]];
// cell name
NSString *cellName = [[NSString alloc] initWithFormat:@"cell%d", i];
// construct each cell's vertical constraint to add it strVerticalConstraint
NSString *viewName = nil;
if(i < numItems - 1)
viewName = [[NSString alloc] initWithFormat:@"[%@(50)]-10-", cellName];
else
viewName = [[NSString alloc] initWithFormat:@"[%@(50)]", cellName];
[strVerticalConstraint appendString:viewName];
// add cell name to dictionary
[subviews setValue:cell forKey:cellName];
[strVerticalConstraint appendString:@"|"];
NSLog(@"strVerticalConstraint: \n%@", strVerticalConstraint);
// Finally, use the long vertical constraint string
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:strVerticalConstraint options:0 metrics:nil views:subviews]];
@end
单元格头文件
#import <UIKit/UIKit.h>
@interface Cell : UIView
@property (nonatomic, strong) UIImageView *leftImage;
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong) UIImageView *rightImage;
@end
单元实现文件
#import "Cell.h"
@implementation Cell
-(id)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if(self)
[self initViews];
[self initConstraints];
return self;
-(void)initViews
self.backgroundColor = [UIColor grayColor];
self.leftImage = [[UIImageView alloc] init];
self.leftImage.contentMode = UIViewContentModeScaleAspectFill;
self.leftImage.clipsToBounds = YES;
self.leftImage.layer.cornerRadius = 10.0;
self.textLabel = [[UILabel alloc] init];
self.textLabel.numberOfLines = 0;
self.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.rightImage = [[UIImageView alloc] init];
self.rightImage.contentMode = UIViewContentModeScaleAspectFill;
self.rightImage.layer.cornerRadius = 10.0;
self.rightImage.clipsToBounds = YES;
[self addSubview:self.leftImage];
[self addSubview:self.textLabel];
[self addSubview:self.rightImage];
-(void)initConstraints
self.leftImage.translatesAutoresizingMaskIntoConstraints = NO;
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.rightImage.translatesAutoresizingMaskIntoConstraints = NO;
id views = @
@"leftImage": self.leftImage,
@"textLabel": self.textLabel,
@"rightImage": self.rightImage
;
// horizontal constraints
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[leftImage(20)]-10-[textLabel]-[rightImage(20)]-10-|" options:0 metrics:nil views:views]];
// vertical constraints
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.leftImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.rightImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[leftImage(20)]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[rightImage(20)]" options:0 metrics:nil views:views]];
@end
您应该会看到如下内容:
也许您有理由使用滚动视图并手动添加每一行,否则,如果您愿意替代,您可以使用 UICollectionView 或 UITableView。
上面的方法会导致大量的内存使用,你可以想象如果你有 1000 行,应用程序需要计算、渲染和存储 1000 行到内存中。不可扩展,也不可行。
这就是 UITableView 或 UICollectionView 的用武之地,它们会在每个单元格离开屏幕时重用它,您只需要在屏幕上渲染和存储可见单元格。
UICollectionView 演示
所以,如果您想查看 UICollectionView 方法,这是一个演示如何做到这一点:
ViewController 头文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray *items;
@end
ViewController 实现文件
#import "ViewController.h"
#import "CustomCell.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
// --------------------------------------------------------
// Hardcoding 15 sample items as the data source.
// Your data might be from a JSON webservice REST API.
// --------------------------------------------------------
self.items = @[
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer.",
@"This is my example text, it could be longer."
];
- (void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
-(void)initViews
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.minimumInteritemSpacing = 0;
flowLayout.minimumLineSpacing = 10;
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
self.collectionView.backgroundColor = [UIColor whiteColor];
// need to tell CollectionView beforehand the cell class you want to use
[self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:@"cellID"];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.view addSubview:self.collectionView];
-(void)initConstraints
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
id views = @
@"collectionView": self.collectionView
;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[collectionView]|" options:0 metrics:nil views:views]];
#pragma mark - UICollectionView Methods -
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
return self.items.count;
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
// note: reuse identifier must match what you specified in the register cell above
CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellID" forIndexPath:indexPath];
// ---------------------------------------------------------------
// hardcoding images here, you might load your images from JSON
// data using an image caching library like SDWebImage
// ---------------------------------------------------------------
cell.leftImage.image = [UIImage imageNamed:@"leftImage.png"];
// getting text data from data source "self.items"
cell.textLabel.text = self.items[indexPath.row];
cell.rightImage.image = [UIImage imageNamed:@"rightImage.png"];
return cell;
// ----------------------------------------------------------------
// Tells the collection view the width and height of each cell
// ----------------------------------------------------------------
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
CGSize size = CGSizeMake(self.view.frame.size.width, 50.0);
return size;
@end
CustomCell 头文件
#import <UIKit/UIKit.h>
@interface CustomCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *leftImage;
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong) UIImageView *rightImage;
@end
CustomCell 实现文件
#import "CustomCell.h"
@implementation CustomCell
-(id)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if(self)
[self initViews];
[self initConstraints];
return self;
-(void)initViews
self.backgroundColor = [UIColor grayColor];
self.leftImage = [[UIImageView alloc] init];
self.leftImage.contentMode = UIViewContentModeScaleAspectFill;
self.leftImage.clipsToBounds = YES;
self.leftImage.layer.cornerRadius = 10.0;
self.textLabel = [[UILabel alloc] init];
self.textLabel.numberOfLines = 0;
self.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.rightImage = [[UIImageView alloc] init];
self.rightImage.contentMode = UIViewContentModeScaleAspectFill;
self.rightImage.layer.cornerRadius = 10.0;
self.rightImage.clipsToBounds = YES;
[self.contentView addSubview:self.leftImage];
[self.contentView addSubview:self.textLabel];
[self.contentView addSubview:self.rightImage];
-(void)initConstraints
self.leftImage.translatesAutoresizingMaskIntoConstraints = NO;
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.rightImage.translatesAutoresizingMaskIntoConstraints = NO;
id views = @
@"leftImage": self.leftImage,
@"textLabel": self.textLabel,
@"rightImage": self.rightImage
;
// horizontal constraints
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[leftImage(20)]-10-[textLabel]-[rightImage(20)]-10-|" options:0 metrics:nil views:views]];
// vertical constraints
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.leftImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.rightImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[leftImage(20)]" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[rightImage(20)]" options:0 metrics:nil views:views]];
@end
你最终会得到这样的结果:
看起来一样但效率更高:D
不,我没有上传相同的屏幕截图:P
希望有帮助吗?
【讨论】:
哇!这是一个完美的答案!非常感谢你!只有一个问题,我被细胞的动态高度困住了。看到您的回答,似乎单元格的高度是固定的,80(使用 ScrollView)和 50(使用 Collections)。但是我的文本是可变的,所以它的高度可能会改变,我知道如何改变它,但我不知道如何相应地改变单元格的高度。 例如,使用 ScrollView,我使用 boundingRectWithSize 计算文本的高度,例如 40,然后我执行 viewName = [[NSString alloc] initWithFormat:@"[%@( %d)]-10-", 单元名称, alt];但单元格显示的比文本短。奇怪的是,我确定这个 40 高度比标签的“真实”高度短,因为我的文本有 3 行,但标签显示正确。我很困惑。 动态单元格高度令人头疼。如果您能够使您的应用程序使用最低 ios 版本 8,那么 iOS 8 UITableView 为您提供动态单元格高度,这样您就不必做太多事情,否则,它会变得更加困难,特别是如果您有一个复杂的细胞。以上是关于在 UIScrollView 子视图中使用自动布局(以编程方式)的主要内容,如果未能解决你的问题,请参考以下文章
XCode - UIScrollView 分页不显示子视图(自动布局?)
具有动态视图数量和自动布局 iOS6 的 UIScrollView