具有清晰背景的 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 我从来没有注意到这一点。我的猜测是拇指只是一个透明的图像,就像原始问题(第一个屏幕截图)一样。诀窍在于对可调整大小的minimumTrackImage
和maximumTrackImage
进行切片。前者的右端和后者的左端是透明的,大小固定——正好是拇指圆直径的一半。
@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 的 minView 超出了它的边界和拇指的中心