使用 UIPanGestureRecognizer 垂直移动 UILabel 时,如何阻止它们在任一方向上走得太远?

Posted

技术标签:

【中文标题】使用 UIPanGestureRecognizer 垂直移动 UILabel 时,如何阻止它们在任一方向上走得太远?【英文标题】:When moving a UILabel vertically with a UIPanGestureRecognizer, how do I stop them from going too far in either direction? 【发布时间】:2013-10-29 14:16:13 【问题描述】:

我试图通过将UIPanGestureRecognizer 附加到UILabel 并随后将约束的常量从UILabel 更改为,使用户能够在视图中上下移动UILabel其视图的顶部。所以基本上如果手势识别器检测到它们向下移动 12pts,则将约束的常量移动 12pts 以移动 UILabel

但是,我希望它们在到达某个垂直点(太高或太低)时停止移动。我可以只检查平移手势的翻译,但我的UILabel 可以是任意数量的行,所以如果是五行而不是一条,显然它不能平移那么远,所以我不能依赖关于平移手势的翻译,我必须考虑到标签的大小。

所以我开始监视它的框架,它运行良好,但在我的实现中,有一个令人讨厌的结果,如果它们完全平移到底部极限,它们必须在UILabel“赶上”之前向后平移很远并附带它(尽管当它们到达顶部边界时不存在此类问题)。基本上,它们向下平移到下限,当它们向后平移时(这都是在同一个手势中),它会暂时“粘住”,直到它们向上平移足够远,然后用手指向上跳。

这是我用来完成此操作的代码:

- (void)textLabelPanned:(UIPanGestureRecognizer *)panGestureRecognizer 
    if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) 
        _textDistanceFromTopBeforeMove = self.textToReadLabelPositionFromTopConstraint.constant;
    
    else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) 
        NSNumber *textDistanceFromTop = @(self.textToReadLabelPositionFromTopConstraint.constant);
        [[NSUserDefaults standardUserDefaults] setObject:textDistanceFromTop forKey:@"TextDistanceFromTop"];
    
    else 
        if (CGRectGetMinY(self.textToReadLabel.frame) >= [UIScreen mainScreen].bounds.origin.y + CLOSEST_TEXT_DISTANCE_TO_TOP && CGRectGetMaxY(self.textToReadLabel.frame) <= [UIScreen mainScreen].bounds.size.height - CLOSEST_TEXT_DISTANCE_TO_BOTTOM) 
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
        
        else if ([panGestureRecognizer translationInView:self.mainView].y > 0) 
            if (CGRectGetMaxY(self.textToReadLabel.frame) + _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y < [UIScreen mainScreen].bounds.size.height - CLOSEST_TEXT_DISTANCE_TO_BOTTOM) 
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
            
        
        else if ([panGestureRecognizer translationInView:self.mainView].y < 0) 
            if (CGRectGetMinY(self.textToReadLabel.frame) + _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y > [UIScreen mainScreen].bounds.origin.y + CLOSEST_TEXT_DISTANCE_TO_TOP) 
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
            
        


        // If one of the options views are present and the user pans really low, hide the options as to allow the user to see where they're panning
        if (_inSpeedChangingMode) 
            if (CGRectGetMaxY(self.textToReadLabel.frame) > CGRectGetMinY(self.progressBar.frame) - 10) 
                [self showWordOptions:nil];
            
        
        else if (_inTextChangingMode) 
            if (CGRectGetMaxY(self.textToReadLabel.frame) > CGRectGetMinY(self.progressBar.frame) - 10) 
                [self showTextOptions:nil];
            
        
    

我到底做错了什么会导致它“粘住”?是否有更好的方法来做到这一点?

【问题讨论】:

【参考方案1】:

您可以完全使用在 Interface Builder 或代码中定义的约束来完成此操作。诀窍是定义约束以防止标签移动到比设置所需位置的约束具有更高优先级的边界。

在我的测试项目中,我完全在一个故事板中设置了一个视图层次结构,它具有 1)视图控制器视图 2)定义边界的“容器视图”3)多行 UILabel。有 6 个约束作用于其容器中的标签:

4 'space to' 约束(前导、尾随、顶部、底部)防止标签被定位在其父容器的边界之外。这些的优先级在 Interface Builder 中设置为默认的“1000”值。这些约束的关系是'>=',常数值是'0'。

2 'space to' 约束(前导、顶部)驱动标签的实际位置。这些的优先级设置得较低;我选择了“500”。这些约束在视图控制器中有出口,因此可以在代码中进行调整。这些约束的关系是'=',初始值是你想要放置标签的任何位置。

标签本身有一个宽度限制,强制它显示多行。

这是 IB 中的样子:

所选约束的优先级较低,用于驱动标签的 x 位置。此约束与视图控制器中的 ivar 相关联,因此可以在运行时进行调整。

选定的约束具有更高的优先级,用于将标签圈在其父视图中。

这是视图控制器中的代码:

@interface TSViewController ()
@end

@implementation TSViewController

    IBOutlet NSLayoutConstraint* _xLayoutConstraint;

    IBOutlet NSLayoutConstraint* _yLayoutConstraint;


- (IBAction) pan: (UIGestureRecognizer*) pgr

    CGPoint p = [pgr locationInView: self.view];

    p.x -= pgr.view.frame.size.width / 2.0;
    p.y -= pgr.view.frame.size.height / 2.0;

    _xLayoutConstraint.constant = p.x;
    _yLayoutConstraint.constant = p.y;


@end

UIPanGestureRecognizer 与 UILabel 相关联,并将其回调设置为视图控制器中的 pan: 方法。

【讨论】:

你能附上你的测试项目吗? 啊,明白了。漂亮的解决方案。【参考方案2】:

如果您的应用具有最低 SDK ios7,您可以使用 UIKit Dynamics 而不是那些 UIGestureRecognizers。使用 UICollisionBehavoir 和 UIAttachmentBehavior 可以轻松解决您的问题

您可能想调查一下。这是 UIKit Dynamics 上的苹果示例项目: https://developer.apple.com/library/IOS/samplecode/DynamicsCatalog/Introduction/Intro.html

试一试,你会惊讶于你可以用这么少的代码做些什么。

WWDC 2013 会议: - UIKit 动态入门 - UIKit Dynamics 的高级技术

【讨论】:

以上是关于使用 UIPanGestureRecognizer 垂直移动 UILabel 时,如何阻止它们在任一方向上走得太远?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 UIPanGestureRecognizer 移动对象时会有延迟?

UIPanGestureRecognizer ***视图未获取事件,子视图使用它们

在 UITableView 上滑动删除以使用 UIPanGestureRecognizer

在 UIPanGestureRecognizer 中使用velocityInView

使用 UIPanGestureRecognizer 拖动 + 旋转触摸下车

使用 UIPanGestureRecognizer 移动和缩放 UIView