Swift 3 - 自定义按钮(图像和文本)只有在没有插座时才能正确绘制?

时间:2018-01-03 22:54:48

标签: swift interface-builder

编辑:虽然我接受了一个帮助我在运行时正确绘制按钮的答案,但我还有其他问题。我怀疑,其根源是的问题,为什么给我的自定义按钮一个出口干扰它的绘制方式。我仍然需要知道为什么会这样。(见下面的答案)

我有自己的按钮类,它扩展了UIButton(见下文)并且有几个IBInspectable属性,如边框宽度/颜色,角半径,甚至渐变背景的开始/结束颜色。我还使用这些属性以编程方式设置图像和标题的插图,以便我可以考虑各种屏幕尺寸。

以前我遇到过一个问题,如果我改变故事板中的“查看为”设备,让我们说从iPhone SE到iPhone 7,然后刷新视图并在物理iPhone SE上运行它将部署的内容iPhone 7,而不是计算物理设备自己的屏幕大小的插图。但是,我几乎通过覆盖我的自定义按钮类中的draw来解决此问题。

我现在的问题是被覆盖的draw方法只被调用(或者似乎仅在有效时才有效)该按钮没有插入其所在的ViewController

例如:

enter image description here

* img是占位符;插图适当地适合真正的imgs。

右下方的按钮有正确绘制的渐变和角落,而其他3则没有。 我已经确认在此按钮上添加一个插座使其行为与其余按钮相同,并且从其他3个插槽中移除插座会导致其正确绘制

作为参考,出口只是

@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
@IBOutlet weak var button3: UIButton!

立即在class MyViewController: UIViewController {声明。

另外,我没有忘记在每个按钮的界面构建器中设置自定义类。

以下是按钮类:

import UIKit

@IBDesignable
class ActionButton: UIButton {

    override init(frame: CGRect){
        super.init(frame: frame)
        setupView()
    }
    required init(coder aDecoder: NSCoder){
        super.init(coder: aDecoder)!
        setupView()
    }

    @IBInspectable var cornerRadius: CGFloat = 0{
        didSet{
            setupView()
        }
    }
    @IBInspectable var startColor: UIColor = UIColor.white{
        didSet{
            setupView()
        }
    }
    @IBInspectable var endColor: UIColor = UIColor.black{
        didSet{
            setupView()
        }
    }
    @IBInspectable var borderWidth: CGFloat = 0{
        didSet{
            self.layer.borderWidth = borderWidth
        }
    }
    @IBInspectable var borderColor: UIColor = UIColor.clear{
        didSet{
            self.layer.borderColor = borderColor.cgColor
        }
    }

    private func setupView(){
        let colors:Array = [startColor.cgColor, endColor.cgColor]
        gradientLayer.colors = colors
        gradientLayer.cornerRadius = cornerRadius
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
        self.setNeedsDisplay()
    }

    var gradientLayer: CAGradientLayer{
        return layer as! CAGradientLayer
    }

    override func draw(_ rect: CGRect) {

        //Draw image

        setupView()

        let btnW = self.frame.size.width
        let btnH = self.frame.size.height

        //Compute and set image insets
        let topBtnInset = btnH * (-5/81)
        let bottomBtnInset = -4 * topBtnInset
        let leftBtnInset = btnW / 4 //btnW * (35/141)
        let rightBtnInset = leftBtnInset

        self.imageEdgeInsets = UIEdgeInsets(top: topBtnInset, left: leftBtnInset, bottom: bottomBtnInset, right: rightBtnInset)

        //Compute and set title insets
        let topTitleInset = btnH * (47/81)
        let bottomTitleInset = CGFloat(0)
        let leftTitleInset = CGFloat(-256) //Image width
        let rightTitleInset = CGFloat(0)

        self.titleEdgeInsets = UIEdgeInsets(top: topTitleInset, left: leftTitleInset, bottom: bottomTitleInset, right: rightTitleInset)

        //Draw text

        //Default font size
        self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 15)

        if(btnH > 81){
            self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
        }else if(btnH > 97){
            self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
        }

    }

    override class var layerClass: AnyClass{
        return CAGradientLayer.self
    }

}

请忽略我处理插图,字体大小等方法。

有谁知道为什么指定按钮的插座使得它不能正确地绘制图层?

2 个答案:

答案 0 :(得分:1)

有几点想法:

  1. draw(_:)方法用于描边路径,绘制图像等。它用于在给定时刻渲染视图。您不应该更新图层,UIKit子视图等。

    在您的代码段中,您的draw(_:)正在调用setupView,然后调用setNeedsDisplay(理论上可以触发draw(_:)再次调用)。我建议仅为那些需要手动绘制的内容保留draw(_:)。 (就像现在一样,你可能完全可以完全消除这种方法,因为这里没有任何东西可以吸引任何东西。)

  2. 对于子视图(其框架等)的任何手动配置,应移至layoutSubviews,只要操作系统确定子视图可能需要frame,就会调用该ActionButton }值已调整。

  3. 如果您对可设计的视图有任何疑问,那么确定您将这些视图放在一个单独的目标中是值得的。它最大限度地减少了在IB中渲染时需要重新编译的内容。它还可以防止主要目标中的问题影响在IB中呈现这些可设计的能力。 (当Apple引入可设计的视图时,它们非常具体,应该将它们放在一个单独的目标中。)

    我在Xcode中也有可设计视图的间歇性问题,但这些问题通常通过退出Xcode,清空派生数据文件夹并重新启动来解决。

  4. 我必须承认,我不知道为什么在可设计视图中添加插座会影响视图的呈现方式。我知道,当我做上述事情时,我无法重现你的问题。

    无论如何,我这样调整你的@import UIKit @IBDesignable public class ActionButton: UIButton { public override init(frame: CGRect = .zero) { super.init(frame: frame) setupView() } public required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! setupView() } @IBInspectable public var cornerRadius: CGFloat = 0 { didSet { setupView() } } @IBInspectable public var startColor: UIColor = .white { didSet { setupView() } } @IBInspectable public var endColor: UIColor = .black { didSet { setupView() } } @IBInspectable public var borderWidth: CGFloat = 0 { didSet { layer.borderWidth = borderWidth } } @IBInspectable public var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } } private func setupView() { let colors = [startColor.cgColor, endColor.cgColor] gradientLayer.colors = colors gradientLayer.cornerRadius = cornerRadius gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0) } private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer } public override func layoutSubviews() { super.layoutSubviews() let btnW = frame.width let btnH = frame.height //Compute and set image insets let topBtnInset = btnH * (-5/81) let bottomBtnInset = -4 * topBtnInset let leftBtnInset = btnW / 4 //btnW * (35/141) let rightBtnInset = leftBtnInset imageEdgeInsets = UIEdgeInsets(top: topBtnInset, left: leftBtnInset, bottom: bottomBtnInset, right: rightBtnInset) //Compute and set title insets let topTitleInset = btnH * (47/81) let bottomTitleInset = CGFloat(0) let leftTitleInset = CGFloat(-256) //THIS MUST BE EQUAL TO -IMAGE WIDTH let rightTitleInset = CGFloat(0) titleEdgeInsets = UIEdgeInsets(top: topTitleInset, left: leftTitleInset, bottom: bottomTitleInset, right: rightTitleInset) //Adjust text font if btnH > 81 { titleLabel?.font = .boldSystemFont(ofSize: 17) } else if btnH > 97 { titleLabel?.font = .boldSystemFont(ofSize: 20) } else { //Default font size titleLabel?.font = .boldSystemFont(ofSize: 15) } } public override class var layerClass: AnyClass { return CAGradientLayer.self } } ,它似乎对我有用:

    def f(x):
        ....
        y = g(x)
        z = tf.matmul(y, w) + b
        return z
    

答案 1 :(得分:0)

我已经接受了Rob的回答,因为它更有可能被其他人使用,但我发现了我的问题,特别是有人遇到类似问题。

显然在我放置这些按钮的View Controller的代码中,我有一个我忘记的功能,它启用/禁用这些自定义按钮,以及设置它们的背景颜色(为纯色)。这就是为什么那些按钮看起来错了。第4个按钮从未受到此功能的影响。这不是其他3个有插座的事实,但当我移除插座时,我评论了对该功能的调用。

然而,在这个问题上解决了其他问题!

- 我有一个非常草率的UIButton实现,它覆盖并且不正确地调用UI函数,导致像旋转时冻结等问题。这些也在Rob的(已接受的)答案中得到了解决。

- Interface Builder中的“刷新所有视图”挂在“签名产品”上,这是由调用self.titleLabel?.font引起的。根据Apple开发人员论坛上的some answers,在layoutSubViews()中执行此操作很糟糕!