iOS中使用滑动手势实现的滚动效果

6

我有一个视图,里面有两个标签。当我向左滑动时,会将下一个内容填充到标签文本中。同样地,向右滑动会加载上一个内容。我想给标签添加一个效果,就好像它们从左边或右边滚动过来一样。

我之前使用了一个scrollview,但是它存在内存问题。所以我现在使用一个视图,通过滑动手势加载下一个或上一个内容。我希望可以将scrollview的滑动效果添加到标签中。我该怎么做?


顺便说一下,虽然我展示了一种在标签下方添加动画的方法,但我不明白为什么你会在滚动视图中遇到“内存问题”。如果你发布那段代码,我们可以帮助你找到内存问题,如果你愿意的话。 - Rob
使用Cover Flow是最好的选择,可以给界面带来漂亮的外观。 - parag
2个回答

15

我不太确定您要实现的具体效果,但是您可以像这样做:创建一个新的临时标签并将其放在屏幕外,然后通过动画将其移动到您屏幕上的标签上方,完成后重新设置旧标签并删除临时标签。以下是一个非自动布局实现的示例:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    UISwipeGestureRecognizer *left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(leftSwipe:)];
    [left setDirection:UISwipeGestureRecognizerDirectionLeft];
    [self.view addGestureRecognizer:left];
    // if non-ARC, release it
    // [release left];

    self.label1.text = @"Mo";
}

- (void)leftSwipe:(UISwipeGestureRecognizer *)gesture
{
    NSString *newText;
    UILabel  *existingLabel = self.label1;

    // in my example, I'm just going to toggle the value between Mo and Curly

    if ([existingLabel.text isEqualToString:@"Curly"])
        newText = @"Mo";
    else
        newText = @"Curly";

    // create new label

    UILabel *tempLabel = [[UILabel alloc] initWithFrame:existingLabel.frame];
    [existingLabel.superview addSubview:tempLabel];
    tempLabel.text = newText;

    // move the new label off-frame to the right

    tempLabel.transform = CGAffineTransformMakeTranslation(tempLabel.superview.bounds.size.width, 0);

    // animate the sliding of them into place

    [UIView animateWithDuration:0.5
                     animations:^{
                         tempLabel.transform = CGAffineTransformIdentity;
                         existingLabel.transform = CGAffineTransformMakeTranslation(-existingLabel.superview.bounds.size.width, 0);
                     }
                     completion:^(BOOL finished) {
                         existingLabel.text = newText;
                         existingLabel.transform = CGAffineTransformIdentity;
                         [tempLabel removeFromSuperview];
                     }];

    // if non-ARC, release it
    // [release tempLabel];
}

这个动画会使标签相对于其父视图进行动画。您可能希望确保将 superview 设置为 "剪切子视图"。这样,动画将受到该 superview 边界的限制,从而产生稍微更加精细的外观。

请注意,如果使用自动布局,想法是相同的(尽管执行起来更加复杂)。基本上配置您的约束条件,以便新视图位于右侧,然后,在动画块中更新/替换约束条件,以使原始标签位于左侧,新标签处于原始标签的位置,最后,在完成块中重置原始标签的约束条件并删除临时标签。


顺便说一句,如果您熟悉内置的转换之一,则所有这些都会变得非常容易:

- (void)leftSwipe:(UISwipeGestureRecognizer *)gesture
{
    NSString *newText;
    UILabel  *existingLabel = self.label1;

    // in my example, I'm just going to toggle the value between Mo and Curly

    if ([existingLabel.text isEqualToString:@"Curly"])
        newText = @"Mo";
    else
        newText = @"Curly";

    [UIView transitionWithView:existingLabel  // or try `existingLabel.superview`
                      duration:0.5
                       options:UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{
                        existingLabel.text = newText;
                    }
                    completion:nil];
}

非常感谢您详细的回答。现在动画已经可以了。但是scrollview的动画更好。scrollview的问题是:在scrollview内部有另一个视图。每当用户滚动时,新视图就会添加到scrollview中。但是在50个视图之后,应用程序会因为内存警告而崩溃。我尝试了一些方法,但没有找到解决方案。所以决定删除scrollview。 - Oktay
@Oktay,听起来你的scrollview代码中有一个简单的bug/泄漏。很多人使用scrollviews没有任何问题。再次提醒,如果你发布你的代码,我认为问题很容易解决。 - Rob
@Oktay,你说你更喜欢滚动视图动画,但我不确定你更喜欢它的哪个方面。因为它使用了平移手势(即一种连续的手势,只要你开始在屏幕上滑动手指,它就开始移动)?我选择了滑动手势而不是平移手势,因为你的标题暗示你想要那样做,但连续的平移手势非常容易实现。或者,你还有其他关于滚动视图的喜好吗? - Rob
我知道你说我的代码有一个错误,但我找不到解决方案。也许我会在另一篇帖子中稍后发送代码。现在我使用滑动方法进行滚动。我认为滚动视图转换在视觉上比滑动动画更好。再次感谢你。 - Oktay
@Rob 你好!非常感谢你的建议。我有完全相同的情况,并且喜欢你的代码。唯一的问题是,在“滚动”视图左侧有一些东西,正如你所指出的那样,它看起来不太美观。你能帮我解决ContainerView的问题吗?谢谢! - Cutetare
1
@Cutetare 只需将标签(如果尚未)放置在您想要约束动画的 UIView 内,并确保其父视图已打开“剪切子视图”设置。我已经修改了上面的代码示例,以便所有动画都与标签的 superview 相关。 - Rob

6

如果您更喜欢更像滚动视图(即通过手势提供连续反馈)的动画效果,则可能如下所示:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandler:)];
    [self.view addGestureRecognizer:pan];

    self.label1.text = @"Mo";
}

- (void)panHandler:(UIPanGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateBegan)
    {
        _panLabel = [[UILabel alloc] init];

        // in my example, I'm just going to toggle the value between Mo and Curly
        // you'll presumably set the label contents based upon the direction of the
        // pan (if positive, swiping to the right, grab the "previous" label, if negative
        // pan, grab the "next" label)

        if ([self.label1.text isEqualToString:@"Curly"])
            _newText = @"Mo";
        else
            _newText = @"Curly";

        // set the text

        _panLabel.text = _newText;

        // set the frame to just be off screen

        _panLabel.frame = CGRectMake(self.label1.frame.origin.x + self.containerView.frame.size.width,
                                     self.label1.frame.origin.y, 
                                     self.label1.frame.size.width, 
                                     self.label1.frame.size.height);

        [self.containerView addSubview:_panLabel];

        _originalCenter = self.label1.center; // save where the original label originally was
    }
    else if (sender.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translate = [sender translationInView:self.containerView];

        if (translate.x > 0)
        {
            _panLabel.center = CGPointMake(_originalCenter.x - self.containerView.frame.size.width + translate.x, _originalCenter.y);
            self.label1.center = CGPointMake(_originalCenter.x + translate.x, _originalCenter.y);
        }
        else 
        {
            _panLabel.center = CGPointMake(_originalCenter.x + self.containerView.frame.size.width + translate.x, _originalCenter.y);
            self.label1.center = CGPointMake(_originalCenter.x + translate.x, _originalCenter.y);
        }
    }
    else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled)
    {
        CGPoint translate = [sender translationInView:self.containerView];
        CGPoint finalNewFieldLocation;
        CGPoint finalOriginalFieldLocation;
        BOOL panSucceeded;

        if (sender.state == UIGestureRecognizerStateFailed ||
            sender.state == UIGestureRecognizerStateCancelled)
        {
            panSucceeded = NO;
        }
        else 
        {
            // by factoring in the velocity, we can capture a flick more accurately
            //
            // (by the way, I don't like iOS's velocity, because if you stop moving, it records the velocity
            // prior to stopping the move rather than noting that you actually stopped, so I usually calculate my own, 
            // but I'll leave this as is for purposes of this example)

            CGPoint velocity  = [sender velocityInView:self.containerView];

            if (translate.x < 0)
                panSucceeded = ((translate.x + velocity.x * 0.5) < -(self.containerView.frame.size.width / 2));
            else
                panSucceeded = ((translate.x + velocity.x * 0.5) > (self.containerView.frame.size.width / 2));
        }

        if (panSucceeded)
        {
            // if we succeeded, finish moving the stuff

            finalNewFieldLocation = _originalCenter;
            if (translate.x < 0)
                finalOriginalFieldLocation = CGPointMake(_originalCenter.x - self.containerView.frame.size.width, _originalCenter.y);
            else
                finalOriginalFieldLocation = CGPointMake(_originalCenter.x + self.containerView.frame.size.width, _originalCenter.y);
        }
        else
        {
            // if we didn't, then just return everything to where it was

            finalOriginalFieldLocation = _originalCenter;

            if (translate.x < 0)
                finalNewFieldLocation = CGPointMake(_originalCenter.x + self.containerView.frame.size.width, _originalCenter.y);
            else
                finalNewFieldLocation = CGPointMake(_originalCenter.x - self.containerView.frame.size.width, _originalCenter.y);
        }

        // animate the moving of stuff to their final locations, and on completion, clean everything up

        [UIView animateWithDuration:0.3
                              delay:0.0 
                            options:UIViewAnimationOptionCurveEaseOut
                         animations:^{
                             _panLabel.center = finalNewFieldLocation;
                             self.label1.center = finalOriginalFieldLocation;
                         }
                         completion:^(BOOL finished) {
                             if (panSucceeded)
                                 self.label1.text = _newText;
                             self.label1.center = _originalCenter;
                             [_panLabel removeFromSuperview];
                             _panLabel = nil; // in non-ARC, release instead
                         }
         ];
    }
}   

请注意,我将原始标签和正在显示的新标签都放在一个容器UIView中(称为containerView),以便我可以将动画限制在该容器内。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接