具有清晰背景的 UISlider 拇指图像在其后面显示轨道

Posted

技术标签:

【中文标题】具有清晰背景的 UISlider 拇指图像在其后面显示轨道【英文标题】:UISlider thumb image with clear background shows track behind it 【发布时间】:2016-07-04 13:19:09 【问题描述】:

如何在UISlider 上设置清晰的拇指背景,以便在拇指后面看不到轨道?

当我使用setThumbImage:forState: 并传递一个部分透明的图像时,滑块的最小轨道和最大轨道图像/颜色在拇指图像后面可见:

我希望它看起来像在 ios 相机应用程序中,其中“透明”拇指图像圈剪辑轨道背景颜色:

【问题讨论】:

@LuizFernandoSalvaterra 有什么问题? @matt 它使缩略图(圆形图像)后面的视图在缩略图移动时透明,就像本机相机应用程序一样 @LuizFernandoSalvaterra 我从来没有注意到这一点。我的猜测是拇指只是一个透明的图像,就像原始问题(第一个屏幕截图)一样。诀窍在于对可调整大小的minimumTrackImagemaximumTrackImage 进行切片。前者的右端和后者的左端是透明的,大小固定——正好是拇指圆直径的一半。 @matt 切片不起作用,因为最大轨道图像会延长轨道的长度。 @AllenHumphreys 是的,我发现了。 【参考方案1】:

TL;DR

简而言之,没有UISlider 的简单 API 可以重现此行为。

前提条件:根据问题,我只是演示如何使拇指显示为透明而不显示轨迹。我没有尝试完全重新创建相机的滑块。

理想的解决方案

理想的解决方案是创建最小和最大轨道图像,利用cap insets 来防止轨道的一部分被填充。我说这是理想的,因为它简单且相对高效。不幸的是,UISlider 做了两件事阻止我们使用这种方法。

    最大轨道图像实际上与整个轨道的宽度相同,但它包含在 clipsToBounds 设置为 true 的视图中。最大轨道的超级视图已调整大小并剪辑最大轨道,以确保它在拇指的前导侧永远不可见。

    在整个层次结构上禁用clipsToBounds 让我们在视图调试器中看到这一点。

    最小轨道UIImageView 被调整大小,但是当一个人在UISlider 上调用setMinimumTrackImage 时,它实际上会创建一个不同的大小调整图像。这个需要一段时间才能弄清楚,但可以通过检查内存地址来证明。

替代解决方案:更改图像以匹配值

根据我从最初调查中学到的知识,我们可以随着值的变化生成图像。这不是最有效的方法,因为它需要一遍又一遍地创建许多图像。但是,它符合原始问题的参数。请注意,您只能使用“连续”滑块有效地做到这一点,因为它依赖于值更新。从好的方面来说,这可以轻松扩展以绘制更有趣的轨迹图像(如相机中的图像)。

这是结果,我把拇指做得很大,这样你就可以很容易地看到它是透明的。

课程如下:

class TransparentThumbSlider: UISlider 

    let thumbWidth: CGFloat = 40

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

        configure()
    

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

        configure()
    

    func configure() 
        setThumbImage(TransparentThumbSlider.thumbImage(diameter: thumbWidth, color: .blue), for: .normal)

        addTarget(self, action: #selector(updateTrackImages), for: .valueChanged)

        updateTrackImages()
    

    static func thumbImage(diameter: CGFloat, color: UIColor) -> UIImage 
        let strokeWidth: CGFloat = 4
        let halfStrokeWidth = strokeWidth / 2
        let totalRect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
        let strokeRect = CGRect(x: halfStrokeWidth,
                                y: halfStrokeWidth,
                                width: diameter - strokeWidth,
                                height: diameter - strokeWidth)

        let renderer = UIGraphicsImageRenderer(size: totalRect.size)
        let image = renderer.image  context in
            context.cgContext.setStrokeColor(color.cgColor)
            context.cgContext.setLineWidth(strokeWidth)
            context.cgContext.strokeEllipse(in: strokeRect)
        

        return image
    

    static func trackImage(colored color: UIColor,
                           thumbWidth: CGFloat,
                           trackWidth: CGFloat,
                           value: CGFloat,
                           flipped: Bool) -> UIImage 

        let fraction = flipped ? 1 - value : value
        let adjustableWidth = (trackWidth - thumbWidth)
        let halfThumbWidth = thumbWidth / 2
        let fudgeFactor: CGFloat = 2

        var totalRect: CGRect

        var coloredRect: CGRect
        if flipped 
            totalRect = CGRect(x: 0, y: 0, width: trackWidth, height: 2)
            coloredRect = CGRect(
                x: adjustableWidth * value + thumbWidth - fudgeFactor,
                y: 0,
                width: adjustableWidth * fraction + (halfThumbWidth - fudgeFactor),
                height: 2)
         else 
            totalRect = CGRect(x: 0, y: 0, width: adjustableWidth * fraction + halfThumbWidth, height: 2)
            coloredRect = CGRect(x: 0, y: 0, width: adjustableWidth * fraction, height: 2)
        

        let renderer = UIGraphicsImageRenderer(size: totalRect.size)
        let image = renderer.image  context in
            context.cgContext.setFillColor(color.cgColor)
            context.fill(coloredRect)
        

        return image
    

    @objc func updateTrackImages() 

        let trackWidth = trackRect(forBounds: bounds).width

        let minimumImage = TransparentThumbSlider.trackImage(
            colored: .green,
            thumbWidth: thumbWidth,
            trackWidth: trackWidth,
            value: CGFloat(value),
            flipped: false)
        setMinimumTrackImage(minimumImage, for: .normal)

        let maximumImage = TransparentThumbSlider.trackImage(
            colored: .yellow,
            thumbWidth: thumbWidth,
            trackWidth: trackWidth,
            value: CGFloat(value),
            flipped: true)
        setMaximumTrackImage(maximumImage, for: .normal)
    

    var previousBounds: CGRect = .zero

    override func layoutSubviews() 
        super.layoutSubviews()

        // Prevent layout loop
        if previousBounds != bounds 
            previousBounds = bounds
            updateTrackImages()
        
    

【讨论】:

我会说(a)你在这个上加倍努力,(b)你应该得到赏金。【参考方案2】:

让我们从它的实际操作开始,以证明这应该实现 OP 的目标:

简短的版本是我使用背景的 UIImage,并应用以下扩展来添加边框。

extension UIImage 
    func addRoundBorder(width: CGFloat,
                        color: UIColor) -> UIImage 
        let square = CGSize(width: min(size.width, size.height) + width * 2,
                            height: min(size.width, size.height) + width * 2)
        let imageView = UIImageView(frame: CGRect(origin: .zero, size: square))
        imageView.contentMode = .center
        imageView.image = self
        imageView.layer.cornerRadius = square.width / 2
        imageView.layer.masksToBounds = true
        imageView.layer.borderWidth = width
        imageView.layer.borderColor = color.cgColor
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        guard let context = UIGraphicsGetCurrentContext() else  return self 
        imageView.layer.render(in: context)
        let result = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return result ?? self
    

然后,我引入背景的UIImage 并调用扩展程序,将结果用作我的新缩略图。

let image = UIImage(named: "circle")!.addRoundBorder(width: 3.0, color: .yellow)
slider.setThumbImage(image, for: .normal) 

虽然这仅用于演示目的,但我建议使用 #imageLiteral 来避免强制施法。

以下屏幕截图将 OP 通过 iOS 相机应用程序提供的示例图像与我实现的示例图像进行比较,如下所示。由于每个尺寸的大小,Apple 的质量允许在比较中提供更好的细节。

关于@Alladinian 的评论,我看到的问题是背景具有浅纹理,并且边框的使用提供了一种视觉方式来打破按钮与背景合并的位置。通过使用 OP 的背景,它似乎提供了类似的视觉效果。然而,更具挑战性的背景(例如图像)肯定会暴露我的设计并产生不良结果。

【讨论】:

只是要注意(一开始我也被这个问题弄糊涂了)OP 想要一个 transparent 拇指穿过轨道(想象一个视频正在全屏播放在背景中 - 就像相机应用程序中的缩放滑块一样 - 您应该能够看到拇指内的动态内容),而不仅仅是通过匹配背景来产生透明错觉的静态图像...... @Alladinian 我明白了。我想这取决于滑块后面的最终背景。假设发布的示例是灰色纹理背景,这不是问题。但是,如果有照片/视频等,那么我的回答也会失败。在这种情况下,他们似乎需要构建一个自定义滑块。【参考方案3】:

据我所知,您必须添加只有圆形边框并且是透明的唯一可能选项的拇指图像

【讨论】:

以上是关于具有清晰背景的 UISlider 拇指图像在其后面显示轨道的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 UISlider 拇指位置

UISlider 突出显示状态,拇指宽度与正常不同

UISlider 的 minView 超出了它的边界和拇指的中心

UISlider 拇指图像像素化

在 UISlider 的缩略图上不断更改 UILabel 的值

调整 UISlider 拇指图像