使用iOS自动布局集中一些图像的最佳方式



我正在做这件事,我很好奇这是最好的方式,还是愚蠢的方式!

我有一堆40像素宽的图像,每一张都像一个拼字游戏方块。我的应用程序希望显示一些并将它们居中显示在屏幕上。只是它不知道会有多少!可能在3到10之间。

所以我认为最好的办法是,如果我数出多少,乘以40,这样我就知道整个东西会有多少像素宽,然后让我们假设它是280像素-我会创建一个280像素宽的UIView,把所有的瓷砖都粘在那里,然后使用Autolayout将UIView居中放置在设备上。

这样,如果用户旋转设备,没问题!

这是最好的方法吗?此外,我还需要让用户将平铺从UIView中拖动到屏幕上的另一个位置。这可能吗?

三种方法向我袭来:

  1. 我认为您使用容器视图的解决方案非常好。但是,您不必在确定图像的大小时手忙脚乱。你只需要定义容器和图像视图之间的关系,它就会调整容器的大小,使其符合图像视图的固有大小(或者,如果你明确定义了图像视图的大小,那也没关系)。然后你可以将容器居中(不给它任何明确的宽度/高度限制):

    // create container
    UIView *containerView = [[UIView alloc] init];
    containerView.backgroundColor = [UIColor clearColor];
    containerView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:containerView];
    // create image views
    UIImageView *imageView1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1.png"]];
    imageView1.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:imageView1];
    UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"2.png"]];
    imageView2.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:imageView2];
    NSDictionary *views = NSDictionaryOfVariableBindings(containerView, imageView1, imageView2);
    // define the container in relation to the two image views 
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView1]-[imageView2]|" options:0 metrics:nil views:views]];
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[imageView1]-|" options:0 metrics:nil views:views]];
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[imageView2]-|" options:0 metrics:nil views:views]];
    // center the container
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:containerView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:containerView.superview
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0
                                                           constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:containerView
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:containerView.superview
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1.0
                                                           constant:0]];
    
  2. 另一个有约束的常见解决方案是创建两个额外的UIView对象(有时称为"间隔视图"),您将为其指定[UIColor clearColor]的背景色,并将它们放在图像视图的左侧和右侧,将它们定义为超视图的边缘,并将右视图定义为与左视图相同的宽度。虽然我确信你正在构建你的约束,但如果我们要为两个以屏幕为中心的图像视图编写视觉格式语言(VFL),它可能看起来像:

    @"H:|[leftView][imageView1]-[imageView2][rightView(==leftView)]|"
    
  3. 或者,您可以通过使用constraintWithItem创建NSLayoutAttributeCenterX约束,并为各种图像视图指定multiplier,使它们按您想要的方式间隔,从而消除对容器视图或左右两个间隔视图的需要。虽然这种技术消除了对这两个间隔视图的需要,但我也认为它有点不直观。

    但它可能看起来像:

    [imageViewArray enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:NSLayoutAttributeCenterX
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:view.superview
                                                                      attribute:NSLayoutAttributeCenterX
                                                                     multiplier:2.0 * (idx + 1) / ([imageViewArray count] + 1)
                                                                       constant:0];
        [view.superview addConstraint:constraint];
    }];
    

    诚然,这采用了略微不同的图像视图间距,但在某些情况下,这是可以的。

就我个人而言,我倾向于第一种方法,但这些方法中的任何一种都有效。

如果您有网格布局,最好的解决方案是使用UICollectionView。这是一个高度可定制的类,几乎可以为任何网格布局需求进行配置。

我还没有找到比WWDC 2012视频更好的UICollectionView介绍:

WWDC 2012年第205届会议:介绍Olivier Gutknecht和Luke Hiesterman的收藏观点WWDC 2012年第219期:高级收藏视图和建筑自定义布局,作者Luke the Hiesterman

Ray Wenderlich提供了一个很好的基于网络的教程:http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12

顺便说一句,我注意到您在问题结束时问了第二个问题,即如何将图像视图从容器中拖出来。

让我们假设您已经按照问题中的建议完成了约束,瓦片位于以主视图为中心的容器视图中(请参阅我的另一个答案的选项1)。您可能会编写一个手势识别器处理程序,当您开始拖动时,它会从容器的tiles列表中删除瓦片,然后相应地动画更新约束:

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static CGPoint originalCenter;
    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        // move the gesture.view out of its container, and up to the self.view, so that as the container
        // resizes, this view we're dragging doesn't move in the process, too
        originalCenter = [self.view convertPoint:gesture.view.center fromView:gesture.view.superview];
        [self.view addSubview:gesture.view];
        gesture.view.center = originalCenter;
        // now update the constraints for the views still left in the container
        [self removeContainerTileConstraints];
        [self.tiles removeObject:gesture.view];
        [self createContainerTileConstraints];
        [UIView animateWithDuration:0.5 animations:^{
            [self.containerView layoutIfNeeded];
        }];
    }
    CGPoint translate = [gesture translationInView:gesture.view];
    gesture.view.center = CGPointMake(originalCenter.x + translate.x, originalCenter.y + translate.y);
    if (gesture.state == UIGestureRecognizerStateEnded)
    {
        // do whatever you want when you drop your tile, presumably changing
        // the superview of the tile to be whatever view you dropped it on
        // and then adding whatever constraints you need to make sure it's
        // placed in the right location.
    }
}

这将优雅地设置平铺(以及它们的容器视图)的动画,以反映您已将平铺拖出容器。

就上下文而言,我将向您展示我是如何创建容器和将与上述手势识别器处理程序一起使用的瓦片的。假设您有一个NSMutableArray,称为tiles,它是您的拼字游戏风格的瓦片,位于您的容器中。然后,您可以创建容器、瓦片,并在每个瓦片上附加一个手势识别器,如下所示:

// create the container
UIView *containerView = [[UIView alloc] init];
containerView.backgroundColor = [UIColor lightGrayColor];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:containerView];
self.containerView = containerView;  // save this for future reference
// center the container (change this to place it whereever you want it)
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:containerView
                                                      attribute:NSLayoutAttributeCenterX
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:containerView.superview
                                                      attribute:NSLayoutAttributeCenterX
                                                     multiplier:1.0
                                                       constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:containerView
                                                      attribute:NSLayoutAttributeCenterY
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:containerView.superview
                                                      attribute:NSLayoutAttributeCenterY
                                                     multiplier:1.0
                                                       constant:0]];
// create the tiles (in my case, three random images), populating an array of `tiles` that
// will specify which tiles the container will have constraints added
self.tiles = [NSMutableArray array];
NSArray *imageNames = @[@"1.png", @"2.png", @"3.png"];
for (NSString *imageName in imageNames)
{
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:imageView];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    [imageView addGestureRecognizer:pan];
    imageView.userInteractionEnabled = YES;
    [self.tiles addObject:imageView];
}
// add the tile constraints
[self createContainerTileConstraints];

你显然需要这些实用的方法:

- (void)removeContainerTileConstraints
{
    NSMutableArray *constraintsToRemove = [NSMutableArray array];
    // build an array of constraints associated with the tiles
    for (NSLayoutConstraint *constraint in self.containerView.constraints)
    {
        if ([self.tiles indexOfObject:constraint.firstItem]  != NSNotFound ||
            [self.tiles indexOfObject:constraint.secondItem] != NSNotFound)
        {
            [constraintsToRemove addObject:constraint];
        }
    }
    // now remove them
    [self.containerView removeConstraints:constraintsToRemove];
}
- (void)createContainerTileConstraints
{
    [self.tiles enumerateObjectsUsingBlock:^(UIView *tile, NSUInteger idx, BOOL *stop) {
        // set leading constraint
        if (idx == 0)
        {
            // if first tile, set the leading constraint to its superview
            [tile.superview addConstraint:[NSLayoutConstraint constraintWithItem:tile
                                                                       attribute:NSLayoutAttributeLeading
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:tile.superview
                                                                       attribute:NSLayoutAttributeLeading
                                                                      multiplier:1.0
                                                                        constant:0.0]];
        }
        else
        {
            // if not first tile, set the leading constraint to the prior tile
            [tile.superview addConstraint:[NSLayoutConstraint constraintWithItem:tile
                                                                       attribute:NSLayoutAttributeLeading
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:self.tiles[idx - 1]
                                                                       attribute:NSLayoutAttributeTrailing
                                                                      multiplier:1.0
                                                                        constant:10.0]];
        }
        // set vertical constraints
        NSDictionary *views = NSDictionaryOfVariableBindings(tile);
        [tile.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[tile]|" options:0 metrics:nil views:views]];
    }];
    // set the last tile's trailing constraint to its superview
    UIView *tile = [self.tiles lastObject];
    [tile.superview addConstraint:[NSLayoutConstraint constraintWithItem:tile
                                                               attribute:NSLayoutAttributeTrailing
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:tile.superview
                                                               attribute:NSLayoutAttributeTrailing
                                                              multiplier:1.0
                                                                constant:0.0]];
}

最新更新