自定义 UIButton 的边缘在与它交互后看起来像素化

Posted

技术标签:

【中文标题】自定义 UIButton 的边缘在与它交互后看起来像素化【英文标题】:Custom UIButton's edges look pixelated after interacting with it 【发布时间】:2019-07-01 14:32:59 【问题描述】:

我创建了一个带有圆角、渐变背景和阴影的自定义 UIButton 子类。按钮在启动应用程序后立即看起来没问题并且抗锯齿,但是如果我按下一个按钮,它的边缘就会变得像素化。

我已经尝试了很多东西,比如在按钮的图层上设置 .allowsEdgeAntialiasing = true 或从“突出显示”设置器中删除缩放变换动画等,但没有任何帮助:(

这是我的按钮类:

@IBDesignable class CircleTintedButton: UIButton 
    @IBInspectable var cornerRadius : CGFloat = 1.0
    @IBInspectable var shadowOffsetWidth: CGFloat = 0.0
    @IBInspectable var shadowOffsetHeight: CGFloat = 2.0
    @IBInspectable var shadowColor : UIColor = UIColor.gray
    @IBInspectable var shadowOpacity: CGFloat = 0.3

    @IBInspectable var startColor: UIColor = .blue 
        didSet 
            setNeedsLayout()
        
    

    @IBInspectable var endColor: UIColor = .green 
        didSet 
            setNeedsLayout()
        
    

    override func layoutSubviews() 
        super.layoutSubviews()

        layer.cornerRadius = cornerRadius
        layer.shadowColor = shadowColor.cgColor
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
        let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
        layer.shadowPath = shadowPath.cgPath
        layer.shadowOpacity = Float(shadowOpacity)

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]        
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.6)

        gradientLayer.locations = [0, 1]
        gradientLayer.frame = bounds
        gradientLayer.cornerRadius = cornerRadius
        gradientLayer.masksToBounds = true

        layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)        
    

    override var isHighlighted: Bool 
        get 
            return super.isHighlighted
        
        set 
            let xScale : CGFloat = newValue ? 1.025 : 1.0
            let yScale : CGFloat = newValue ? 1.1 : 1.0
            UIView.animate(withDuration: 0.1) 
                let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
                self.transform = transformation
            

            super.isHighlighted = newValue
        
    


我的测试设备(iPhone 7 @ 12.1.2)的一些屏幕截图:

应用启动后: https://vinishko.party/files/ok.jpg

按下此按钮后: https://vinishko.party/files/aliased.jpg

花了一整天的时间试图解决这个问题,请帮助我:D 谢谢。

【问题讨论】:

【参考方案1】:

每次调用layoutSubviews() 时,您都会添加另一个渐变层。您可以通过为您的按钮添加@IBAction 来确认这一点:

@IBAction func didTap(_ sender: Any) 

    if let b  = sender as? CircleTintedButton 
        let n = b.layer.sublayers?.count
        print("nunLayers: \(String(describing: n))")
    


您会看到子层数随着每次点击而增加。

添加一个渐变层作为自定义按钮的 var / 属性,然后只添加一次:

@IBDesignable class CircleTintedButton: UIButton 
    @IBInspectable var cornerRadius : CGFloat = 1.0
    @IBInspectable var shadowOffsetWidth: CGFloat = 0.0
    @IBInspectable var shadowOffsetHeight: CGFloat = 2.0
    @IBInspectable var shadowColor : UIColor = UIColor.gray
    @IBInspectable var shadowOpacity: CGFloat = 0.3

    // add this var / property
    private var gradLayer: CAGradientLayer?

    @IBInspectable var startColor: UIColor = .blue 
        didSet 
            setNeedsLayout()
        
    

    @IBInspectable var endColor: UIColor = .green 
        didSet 
            setNeedsLayout()
        
    

    override func layoutSubviews() 
        super.layoutSubviews()

        layer.cornerRadius = cornerRadius
        layer.shadowColor = shadowColor.cgColor
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
        let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
        layer.shadowPath = shadowPath.cgPath
        layer.shadowOpacity = Float(shadowOpacity)

        // only create / add the gradient layer once
        if gradLayer == nil 

            let gradientLayer = CAGradientLayer()

            gradientLayer.locations = [0, 1]
            gradientLayer.masksToBounds = true

            layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)

            self.gradLayer = gradientLayer

        

        gradLayer?.colors = [startColor.cgColor, endColor.cgColor]
        gradLayer?.startPoint = CGPoint(x: 0.0, y: 0.0)
        gradLayer?.endPoint = CGPoint(x: 1.0, y: 0.6)

        gradLayer?.frame = bounds
        gradLayer?.cornerRadius = cornerRadius

// original code
//      let gradientLayer = CAGradientLayer()
//      gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
//      gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
//      gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.6)
//
//      gradientLayer.locations = [0, 1]
//      gradientLayer.frame = bounds
//      gradientLayer.cornerRadius = cornerRadius
//      gradientLayer.masksToBounds = true
//
//      layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)
    

    override var isHighlighted: Bool 
        get 
            return super.isHighlighted
        
        set 
            let xScale : CGFloat = newValue ? 1.025 : 1.0
            let yScale : CGFloat = newValue ? 1.1 : 1.0
            UIView.animate(withDuration: 0.1) 
                let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
                self.transform = transformation
            

            super.isHighlighted = newValue
        
    

【讨论】:

非常感谢!奇迹般有效。没想到它不仅会被调用一次..

以上是关于自定义 UIButton 的边缘在与它交互后看起来像素化的主要内容,如果未能解决你的问题,请参考以下文章

自定义 UIControl(滑块)在与交互之前不遵守约束

当用户与 UIButton 交互时,是不是可以让我在自定义 UIButton 的 drawRect 中绘制的内容“变暗”?

使用 Lambda@Edge 在边缘站点自定义内容

Ag-Grid 在与下拉列表交互期间保持自定义过滤器打开

如何动态更改自定义 UIButton 图像

自定义 UIButton 类 - 渐变离开按钮边框