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

时间:2013-08-30 03:23:12

标签: ios objective-c autolayout

我这样做,我很好奇这是最好的方式还是愚蠢的方式!

我有一堆40像素宽的图像,每一个像拼字游戏图块。我的应用程序想要显示一些并将它们置于屏幕中心。只有它不知道会有多少!可能在3到10之间。

所以我认为最好的事情是,如果我计算多少,40乘以,所以我知道整个事物将有多少像素宽,然后让我们假设它是280像素 - 我将创建一个280像素宽的UIView,棒那里的所有瓷砖,然后使用Autolayout将UIView集中在设备上。

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

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

3 个答案:

答案 0 :(得分:4)

三种方法向我跳出来:

  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. 或者,您可以通过使用NSLayoutAttributeCenterX创建constraintWithItem约束并指定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];
    }];
    

    这无疑使用的图像视图间距略有不同,但在某些情况下它很好。

  4. 就个人而言,我倾向于采用第一种方法,但其中任何一种方法都有效。

答案 1 :(得分:0)

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

我还没有比WWDC 2012视频更好地了解UICollectionView可以做的事情:

WWDC 2012 Session 205: Introducing Collection Views by Olivier Gutknecht and Luke Hiesterman WWDC 2012 Session 219: Advanced Collection Views and Building Custom Layouts by Luke the Hiesterman

来自Ray Wenderlich的精彩网络教程在这里: http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12

答案 2 :(得分:0)

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

让我们假设您已经完成了问题中建议的约束,其中磁贴位于容器视图中,您将主要放在主视图上(请参阅我的其他答案的选项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]];

}