alskipp/ASValueTrackingSlider

Option to make the rect underneath the slider?

Closed this issue · 1 comments

I have a UISlider at the very top of the screen. It would be really great if there was an option to have it underneath the slider. I kinda sorta managed it by modifying line 219 in ASValuePopUpView.m:
CGPoint p0 = CGPointMake(arrowX, -(CGRectGetMaxY(self.bounds)));

and line 221 in ASValueTrackingSlider.m to:
popUpRect.origin.y = (thumbRect.origin.y - _popUpViewSize.height) * -1

but this feels a bit hackish to me. I'm also now getting the wide end of the arrow cutting through the rect and I'm not sure how to correct that.

Thanks for raising the issue. As you've discovered the main limitation of this control is that it can't be used effectively at the very top of the screen. The option to show the popup view beneath the slider would be possible, but it's not something I'd consider adding because it's not a particularly elegant solution.

The user's finger would be partially obscuring the popup view making it difficult to read, which would be very annoying (and you don't want to be annoying the users of your app). If you really want to show a popup view above the slider, my suggestion would be to redesign your view to give the slider more space.

If you're insistent, the functionality you asked for requires changes to six lines of code, but it does affect several methods. Instead of trying to explain where all the adjustments need to be made, I've just included the methods which have changed. However, due to the reason outlined above, I strongly recommend that you do not use the code below!

ASValuePopUpView.m

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.anchorPoint = CGPointMake(0.5, 0);

        self.userInteractionEnabled = NO;
        _backgroundLayer = [CAShapeLayer layer];
        _backgroundLayer.anchorPoint = CGPointMake(0, 0);

        _textLayer = [CATextLayer layer];
        _textLayer.alignmentMode = kCAAlignmentCenter;
        _textLayer.anchorPoint = CGPointMake(0, 0);
        _textLayer.contentsScale = [UIScreen mainScreen].scale;
        _textLayer.actions = @{@"bounds" : [NSNull null],   // prevent implicit animation of bounds
                               @"position" : [NSNull null]};// and position

        [self.layer addSublayer:_backgroundLayer];
        [self.layer addSublayer:_textLayer];

        _attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:nil];
    }
    return self;
}

- (void)setArrowCenterOffset:(CGFloat)offset
{
    // only redraw if the offset has changed
    if (_arrowCenterOffset != offset) {
        _arrowCenterOffset = offset;

        // the arrow tip should be the origin of any scale animations
        // to achieve this, position the anchorPoint at the tip of the arrow
        self.layer.anchorPoint = CGPointMake(0.5+(offset/self.bounds.size.width), 0);
        [self drawPath];
    }
}

- (void)drawPath
{
    // Create rounded rect
    CGRect roundedRect = self.bounds;
    roundedRect.size.height -= ARROW_LENGTH;
    UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:4.0];

    // Create arrow path
    UIBezierPath *arrowPath = [UIBezierPath bezierPath];
    CGFloat arrowX = CGRectGetMidX(self.bounds) + _arrowCenterOffset;
    CGPoint p0 = CGPointMake(arrowX, CGRectGetMaxY(self.bounds));
    [arrowPath moveToPoint:p0];
    [arrowPath addLineToPoint:CGPointMake((arrowX - 6.0), CGRectGetMaxY(roundedRect))];
    [arrowPath addLineToPoint:CGPointMake((arrowX + 6.0), CGRectGetMaxY(roundedRect))];
    [arrowPath closePath];

    // combine arrow path and rounded rect
    [roundedRectPath appendPath:arrowPath];

    [roundedRectPath applyTransform:CGAffineTransformMakeScale(1.0, -1.0)];
    [roundedRectPath applyTransform:CGAffineTransformMakeTranslation(0, CGRectGetHeight(self.bounds))];

    _backgroundLayer.path = roundedRectPath.CGPath;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // only redraw if the view size has changed
    if (!CGSizeEqualToSize(self.bounds.size, _oldSize)) {
        _oldSize = self.bounds.size;
        _backgroundLayer.bounds = self.bounds;

        CGFloat textHeight = [_textLayer.string size].height;
        CGRect textRect = CGRectMake(self.bounds.origin.x,
                                     (self.bounds.size.height+ARROW_LENGTH-textHeight)/2,
                                     self.bounds.size.width, textHeight);
        _textLayer.frame = textRect;
        [self drawPath];
    }
}

ASValueTrackingSlider.m

- (void)adjustPopUpViewFrame
{
    CGRect thumbRect = [self thumbRect];
    CGFloat thumbW = thumbRect.size.width;
    CGFloat thumbH = thumbRect.size.height;

    CGRect popUpRect = CGRectInset(thumbRect, (thumbW - _popUpViewSize.width)/2, (thumbH - _popUpViewSize.height)/2);
    popUpRect.origin.y = CGRectGetMaxY(thumbRect);

    // determine if popUpRect extends beyond the frame of the UISlider
    // if so adjust frame and set the center offset of the PopUpView's arrow
    CGFloat minOffsetX = CGRectGetMinX(popUpRect);
    CGFloat maxOffsetX = CGRectGetMaxX(popUpRect) - self.bounds.size.width;

    CGFloat offset = minOffsetX < 0.0 ? minOffsetX : (maxOffsetX > 0.0 ? maxOffsetX : 0.0);
    popUpRect.origin.x -= offset;
    [self.popUpView setArrowCenterOffset:offset];

    self.popUpView.frame = popUpRect;
}