You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现无需触摸UISlider滑块即可在UICollectionViewCell内拖动

解决方案:UICollectionViewCell内自定义UISlider实现全域平滑拖动

需求:在UICollectionViewCell中自定义了旋转90度的UISlider,希望触摸单元格任意区域(紫色区域)即可拖动滑块,且滑块从当前位置平滑滑动,而非直接跳转至触摸位置。

问题分析

现有代码仅在触摸滑块thumb附近区域时触发跟踪逻辑,且默认UISlider触摸非thumb区域会直接将value跳转到触摸位置对应的数值,无法实现平滑过渡。需要修改触摸跟踪逻辑,通过计算触摸偏移量逐步更新slider的value。

修改后的实现代码

@implementation CDCFaderSlider {
    CGPoint _initialTouchPoint;
    float _initialValue;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame: frame];
    
    if (self) {
        [self setOpaque: YES];
        [self setTransform: CGAffineTransformMakeRotation((CGFloat)(M_PI * -0.5f))];
        [self constructSlider];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder: aDecoder];
    
    if (self) {
        [self setTransform: CGAffineTransformMakeRotation((CGFloat)(M_PI * -0.5f))];
        [self constructSlider];
    }
    return self;
}

#pragma mark - UIControl touch event tracking

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    _initialTouchPoint = [touch locationInView:self];
    _initialValue = self.value;
    
    // 开启全域触摸跟踪
    [self positionAndUpdateValueView];
    [self fadeValueViewInAndOut: YES];
    return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint currentTouchPoint = [touch locationInView:self];
    // 计算Y轴偏移(滑块旋转90度后,轨道高度对应原X轴长度)
    CGFloat deltaY = currentTouchPoint.y - _initialTouchPoint.y;
    
    // 计算每像素对应的value变化比例
    CGRect trackRect = [self trackRectForBounds:self.bounds];
    CGFloat trackHeight = trackRect.size.height;
    CGFloat valueRange = self.maximumValue - self.minimumValue;
    CGFloat valuePerPixel = valueRange / trackHeight;
    
    // 计算并限制新value范围
    float newValue = _initialValue + deltaY * valuePerPixel;
    newValue = MAX(self.minimumValue, MIN(newValue, self.maximumValue));
    
    // 平滑更新滑块值
    self.value = newValue;
    
    [self positionAndUpdateValueView];
    return YES;
}

- (void)cancelTrackingWithEvent:(UIEvent *)event {
    [self fadeValueViewInAndOut: NO];
    [super cancelTrackingWithEvent: event];
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    [self fadeValueViewInAndOut: NO];
    [super endTrackingWithTouch: touch withEvent: event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self fadeValueViewInAndOut: NO];
    [super touchesEnded: touches withEvent: event];
}

- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent*)event {
    CGRect bounds = self.bounds;
    bounds = CGRectInset(bounds, 0, -70);
    return CGRectContainsPoint(bounds, point);
}

#pragma mark - Helper methods

- (void)constructSlider {
    self.valueView = [[CDCFaderValueView alloc] initWithFrame: CGRectZero];
    self.valueView.backgroundColor = UIColor.clearColor;
    self.valueView.alpha = 0.0;
    self.valueView.transform = CGAffineTransformMakeRotation((CGFloat)(M_PI * 0.5));
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self addSubview: self.valueView];
    });
}

- (void)fadeValueViewInAndOut:(BOOL)aFadeIn {
    [UIView animateWithDuration: 0.5 animations:^{
        self.valueView.alpha = aFadeIn ? 0.8 : 0.0;
    } completion:nil];
}

- (void)positionAndUpdateValueView {
    CGRect thumbRect = self.thumbRect;
    CGRect popupRect = CGRectOffset(thumbRect, floor(thumbRect.size.width * 0.2), -floor(thumbRect.size.height * 0.5));
    self.valueView.frame = CGRectInset(popupRect, -30, -10);
    self.valueView.value = faderDBValues[(NSInteger)self.value];
}

#pragma mark - Property

- (CGRect)thumbRect {
    CGRect trackRect = [self trackRectForBounds: self.bounds];
    return [self thumbRectForBounds:self.bounds trackRect:trackRect value:self.value];
}

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    // 保留原有action节流逻辑
    if (self.count > 4) {
        [super sendAction:action to:target forEvent:event];
        self.count = 0;
    } else {
        self.count++;
    }
    
    if (!self.tracking) {
        [super sendAction:action to:target forEvent:event];
    }
}

@end

关键修改说明

  1. 新增实例变量:_initialTouchPoint和_initialValue记录触摸初始状态,用于后续偏移量计算。
  2. 全域触摸开启:移除beginTrackingWithTouch:中的thumb区域判断,直接返回YES允许任意区域触发跟踪。
  3. 平滑值计算:在continueTrackingWithTouch:中通过触摸偏移量和轨道比例,逐步计算滑块新值,避免直接跳转。
  4. 代码简化:优化fadeValueViewInAndOut:的写法,保持逻辑简洁。

内容的提问来源于stack exchange,提问作者NaKh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:57:01