UISlider 与 ProgressView 相结合

Posted

技术标签:

【中文标题】UISlider 与 ProgressView 相结合【英文标题】:UISlider with ProgressView combined 【发布时间】:2010-12-21 00:54:51 【问题描述】:

有没有苹果自制的方法来获取带有 ProgressView 的 UISlider。这被许多流媒体应用程序使用,例如本机 quicktimeplayer 或 youtube。 (只是为了确定:我只对可视化感兴趣)

干杯西蒙

【问题讨论】:

使用 SDK 中包含的 MPVolumeView developer.apple.com/library/ios/#documentation/mediaplayer/… 【参考方案1】:

这是您所描述内容的简单版本。

它是“简单的”,因为我没有费心尝试添加阴影和其他细微之处。但它很容易构建,如果你愿意,你可以调整它以更微妙的方式绘制。例如,您可以制作自己的图像并将其用作滑块的拇指。

这实际上是一个位于绘制温度计的 UIView 子类 (MyTherm) 之上的 UISlider 子类,以及绘制数字的两个 UILabel。

UISlider 子类消除了内置轨道,因此它后面的温度计可以显示出来。但是 UISlider 的拇指(旋钮)仍然可以按正常方式拖动,并且可以将其设置为自定义图像,在用户拖动时获取 Value Changed 事件,等等。下面是消除自身轨迹的 UISlider 子类的代码:

- (CGRect)trackRectForBounds:(CGRect)bounds 
    CGRect result = [super trackRectForBounds:bounds];
    result.size.height = 0;
    return result;

温度计是自定义 UIView 子类 MyTherm 的一个实例。我在笔尖中实例化它并取消选中它的不透明并给它一个透明颜色的背景颜色。它有一个value 属性,所以它知道温度计要装多少。这是它的drawRect: 代码:

- (void)drawRect:(CGRect)rect 
    CGContextRef c = UIGraphicsGetCurrentContext();
    [[UIColor whiteColor] set];
    CGFloat ins = 2.0;
    CGRect r = CGRectInset(self.bounds, ins, ins);
    CGFloat radius = r.size.height / 2.0;
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, CGRectGetMaxX(r) - radius, ins);
    CGPathAddArc(path, NULL, radius+ins, radius+ins, radius, -M_PI/2.0, M_PI/2.0, true);
    CGPathAddArc(path, NULL, CGRectGetMaxX(r) - radius, radius+ins, radius, M_PI/2.0, -M_PI/2.0, true);
    CGPathCloseSubpath(path);
    CGContextAddPath(c, path);
    CGContextSetLineWidth(c, 2);
    CGContextStrokePath(c);
    CGContextAddPath(c, path);
    CGContextClip(c);
    CGContextFillRect(c, CGRectMake(r.origin.x, r.origin.y, r.size.width * self.value, r.size.height));

要更改温度计值,请将 MyTherm 实例的 value 更改为 0 到 1 之间的数字,并使用 setNeedsDisplay 告诉它重新绘制自身。

【讨论】:

我在 Xcode 5.1 中遇到了无效的上下文错误,因为这条线 result.size.height = 0 @Rams 如何避免呢? 由于覆盖 trackRectForBounds: 并将高度设置为 0,我还遇到了四个与图形上下文相关的错误。为了解决这个问题,我用新的(透明)图像替换了最小和最大轨道图像初始化方法。即[self setMinimumTrackImage:[UIImage alloc] forState:UIControlStateNormal];[self setMaximumTrackImage:[UIImage alloc] forState:UIControlStateNormal]; @orkenstein,我已将其修正为 0.01(非零)。【参考方案2】:

使用标准控件可以做到这一点。

在 Interface Builder 中,将您的 UISlider 立即放在您的 UIProgressView 之上,并使它们的大小相同。

UISlider 上,背景水平线称为轨道,诀窍是使其不可见。我们使用透明 PNG 和 UISlider 方法 setMinimumTrackImage:forState:setMaximumTrackImage:forState: 来做到这一点。

在视图控制器的viewDidLoad 方法中添加:

[self.slider setMinimumTrackImage:[UIImage imageNamed:@"transparent.png"] forState:UIControlStateNormal];
[self.slider setMaximumTrackImage:[UIImage imageNamed:@"transparent.png"] forState:UIControlStateNormal];

其中self.slider 指的是您的UISlider

我已经在 Xcode 中测试了代码,这将为您提供一个带有独立进度条的滑块。

【讨论】:

我采用了这个解决方案,但不是捆绑透明的 png,您可以在运行时使用.. UIGraphicsBeginImageContextWithOptions((CGSize) 1, 1 , NO, 0.0f) 创建一个透明的 1 像素 UIImage ; UIImage *transparentImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 好的。但是传递的视频/音频时长不显示,如何解决? @SamDoshi 我只设置MaximumTrackImage,但它也会影响minimumTrackImage,?【参考方案3】:

适合我设计的解决方案:

class SliderBuffering:UISlider 
    let bufferProgress =  UIProgressView(progressViewStyle: .Default)

    override init (frame : CGRect) 
        super.init(frame : frame)
    

    convenience init () 
        self.init(frame:CGRect.zero)
        setup()
    

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        setup()
    

    func setup() 
        self.minimumTrackTintColor = UIColor.clearColor()
        self.maximumTrackTintColor = UIColor.clearColor()
        bufferProgress.backgroundColor = UIColor.clearColor()
        bufferProgress.userInteractionEnabled = false
        bufferProgress.progress = 0.0
        bufferProgress.progressTintColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.5)
        bufferProgress.trackTintColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
        self.addSubview(bufferProgress)
    
 

【讨论】:

【参考方案4】:

创建一个 UISlider:

// 1
// Make the slider as a public propriety so you can access it
playerSlider = [[UISlider alloc] init];
[playerSlider setContinuous:YES];
[playerSlider setHighlighted:YES];
// remove the slider filling default blue color
[playerSlider setMaximumTrackTintColor:[UIColor clearColor]];
[playerSlider setMinimumTrackTintColor:[UIColor clearColor]];
// Chose your frame
playerSlider.frame = CGRectMake(--- , -- , yourSliderWith , ----);

// 2
// create a UIView that u can access and make it the shadow of your slider 
shadowSlider = [[UIView alloc] init];
shadowSlider.backgroundColor = [UIColor lightTextColor];
shadowSlider.frame = CGRectMake(playerSlider.frame.origin.x , playerSlider.frame.origin.y , playerSlider.frame.size.width , playerSlider.frame.origin.size.height);
shadowSlider.layer.cornerRadius = 4;
shadowSlider.layer.masksToBounds = YES;
[playerSlider addSubview:shadowSlider];
[playerSlider sendSubviewToBack:shadowSlider];


// 3
// Add a timer Update your slider and shadow slider programatically 
 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateSlider) userInfo:nil repeats:YES];


-(void)updateSlider 

    // Update the slider about the music time
    playerSlider.value = audioPlayer.currentTime; // based on ur case 
    playerSlider.maximumValue = audioPlayer.duration;

    float smartWidth = 0.0;
    smartWidth = (yourSliderFullWidth * audioPlayer.duration ) / 100;
    shadowSlider.frame = CGRectMake( shadowSlider.frame.origin.x , shadowSlider.frame.origin.y , smartWidth , shadowSlider.frame.size.height);

   

享受吧!附:我可能有一些错别字。

【讨论】:

【参考方案5】:

想法 1: 您可以通过继承 UISlider 轻松地将其用作进度视图。它响应诸如“setValue:animated:”之类的方法,您可以使用这些方法设置视图的值(即:进度)。

创建您在示例中看到的内容的唯一“限制”是缓冲区栏,您可以通过“创造性地”为 UISlider 蒙皮来创建它(因为您可以向其添加自定义皮肤),并且可能以编程方式设置该皮肤。

想法 2: 另一个(更简单)的选择是继承 UIProgressView,并在该子类中创建一个 UISlider。您可以为 UISlider 设置透明皮肤(没有条,只有旋钮可见)并将其放置在 UIProgressView 上。

您可以使用 UIProgressView 进行预加载(缓冲),使用 UISlider 进行电影控制/进度指示。

看起来相当简单:-)

编辑:要真正回答您的问题,没有内部方法,但使用给定的工具很容易完成。

【讨论】:

对不起,但我不知道如何将 UIProgressView 子类化为 UISlider。尽管您进一步帮助了我并回答了我的问题,但我还是被卡住了。【参考方案6】:

你可以做一些这样的技巧,它更容易理解。只需在您的 UISlider 子类中插入以下代码即可。

- (void)layoutSubviews

    [super layoutSubviews];

    if (_availableDurationImageView == nil) 
        // step 1 
        // get max length that our "availableDurationImageView" will show
        UIView *maxTrackView = [self.subviews objectAtIndex:self.subviews.count - 3];
        UIImageView *maxTrackImageView = [maxTrackView.subviews objectAtIndex:0];
        _maxLength = maxTrackImageView.width;

        // step 2
        // get the right frame where our "availableDurationImageView" will place in       superView
        UIView *minTrackView = [self.subviews objectAtIndex:self.subviews.count - 2];
        _availableDurationImageView = [[UIImageView alloc] initWithImage:[[UIImage     imageNamed:@"MediaSlider.bundle/4_jindu_huancun.png"]     resizableImageWithCapInsets:UIEdgeInsetsMake(0, 2, 0, 2)]];
        _availableDurationImageView.opaque = NO;
        _availableDurationImageView.frame = minTrackView.frame;

        [self insertSubview:_availableDurationImageView belowSubview:minTrackView];
    



- (void)setAvailableValue:(NSTimeInterval)availableValue

    if (availableValue >=0 && availableValue <= 1) 
        // use "maxLength" and percentage to set our "availableDurationImageView" 's length
        _availableDurationImageView.width = _maxLength * availableValue;
    

【讨论】:

【参考方案7】:

添加 matt 的解决方案,注意从 iOS 7.0 开始,实现 trackRectForBounds: 是不可能的。这是我对这个问题的解决方案:

在你的 UISlider 子类中,这样做:

-(void)awakeFromNib

    [super awakeFromNib];

    UIImage* clearColorImage = [UIImage imageWithColor:[UIColor clearColor]];
    [self setMinimumTrackImage:clearColorImage forState:UIControlStateNormal];
    [self setMaximumTrackImage:clearColorImage forState:UIControlStateNormal];

用 imageWithColor 作为这个函数:

+ (UIImage*) imageWithColor:(UIColor*)color

    return [UIImage imageWithColor:color andSize:CGSizeMake(1.0f, 1.0f)];

这将妥善处理这个烦人的 trackRectangle。

我花了太多时间寻找这个问题的解决方案,希望这能为另一个可怜的人节省一些时间;)。

【讨论】:

【参考方案8】:

Here is a solution in Objective C. https://github.com/abhimuralidharan/BufferSlider

这个想法是在 UISlider 子类中创建一个 UIProgressview 作为属性,并以编程方式添加所需的约束。

#import <UIKit/UIKit.h> //.h file

@interface BufferSlider : UISlider
@property(strong,nonatomic) UIProgressView *bufferProgress;
@end

#import "BufferSlider.h" //.m file

@implementation BufferSlider


- (instancetype)initWithFrame:(CGRect)frame

    self = [super initWithFrame:frame];
    if (self) 
        [self setup];
    
    return self;

-(void)setup 
    self.bufferProgress = [[UIProgressView alloc] initWithFrame:self.bounds];
    self.minimumTrackTintColor = [UIColor redColor];
    self.maximumTrackTintColor = [UIColor clearColor];
    self.value = 0.2;
    self.bufferProgress.backgroundColor = [UIColor clearColor];
    self.bufferProgress.userInteractionEnabled = NO;
    self.bufferProgress.progress = 0.7;
    self.bufferProgress.progressTintColor = [[UIColor blueColor] colorWithAlphaComponent:0.5];
    self.bufferProgress.trackTintColor = [[UIColor lightGrayColor] colorWithAlphaComponent:2];
    [self addSubview:self.bufferProgress];
    [self setThumbImage:[UIImage imageNamed:@"redThumb"] forState:UIControlStateNormal];
    self.bufferProgress.translatesAutoresizingMaskIntoConstraints = NO;

    NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:self.bufferProgress attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
    NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:self.bufferProgress attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0.75];  // edit the constant value based on the thumb image
    NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:self.bufferProgress attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];

    [self addConstraints:@[left,right,centerY]];
    [self sendSubviewToBack:self.bufferProgress];

- (instancetype)initWithCoder:(NSCoder *)coder

    self = [super initWithCoder:coder];
    if (self) 
        [self setup];
    
    return self;

@end

【讨论】:

【参考方案9】:

适用于 Swift5

首先,将点击手势添加到滑块:

let tap_gesture = UITapGestureRecognizer(target: self, action: #selector(self.sliderTapped(gestureRecognizer:)))
self.slider.addGestureRecognizer(tap_gesture)

然后,实现这个函数:

@objc func sliderTapped(gestureRecognizer: UIGestureRecognizer) 
    
    let locationOnSlider = gestureRecognizer.location(in: self.slider)
    let maxWidth = self.slider.frame.size.width
    let touchLocationRatio = locationOnSlider.x * CGFloat(self.slider.maximumValue) / CGFloat(maxWidth)
    self.slider.value = Float(touchLocationRatio)
    
    print("New value: ", round(self.slider.value))
   

【讨论】:

以上是关于UISlider 与 ProgressView 相结合的主要内容,如果未能解决你的问题,请参考以下文章

iOS 简易环形进度条

Swift ui macos ProgressView 使用未解析的标识符'ProgressView'

progressView的宽高

在 SwiftUI 中导出文件时显示 ProgressView

如何将 PDF“页码”与 UISlider 链接?

滚动表上的 ProgressView 块