在UIScrollView子视图中使用autolayout(以编程方式)

时间:2014-10-22 09:25:08

标签: ios objective-c uiscrollview autolayout

首先,我一直在阅读与此类似的所有问题,但没有成功,所以最后我会尝试询问我的具体案例。

我有一个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中。这样我可以在它们之间使用autolayout而不会出现问题,并且每个UIView内的所有内容都处于正确的位置。

但是我在循环内部的UIViews之间使用autolayout时仍然遇到问题。我这样做:

所有块都没有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];

关于块之间的垂直分隔,当UIView是UIScrollView的第一个块时:

// 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位置,并且还给出了应用约束的错误。 我确定我没有使用正确的约束方法,但我找不到这种用法的例子。

1 个答案:

答案 0 :(得分:7)

看起来你正试图重新发明轮子队友。您可能应该使用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

你应该看到这样的事情:

screenshot1

也许您有理由使用scrollView并手动添加每一行,否则,如果您打开替代方案,则可以使用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

你最终得到这样的东西:

screenshot2

看起来相同但效率更高:D

不,我没有上传相同的屏幕截图:P

希望有帮助吗?