代码之家  ›  专栏  ›  技术社区  ›  Busman

Swift 3-自定义按钮(图像和文本)仅在没有出口时正确绘制?

  •  3
  • Busman  · 技术社区  · 6 年前

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

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

    之前我遇到过一个问题,如果我将情节提要中的“视图为”设备更改为iPhone SE,比如从iPhone SE更改为iPhone 7,然后刷新视图并在物理iPhone SE上运行,它将与iPhone 7的插入一起部署,而不是使用物理设备自己的屏幕大小来计算插入。然而,我 几乎 通过重写 draw 在我的自定义按钮类中。

    我现在的问题是 方法仅被调用(或似乎仅在以下情况下有效) 按钮没有到的出口 ViewController 它所在的类 .

    例如:

    enter image description here

    *img是一个占位符;插图适合真实的IMG。

    右下角的按钮有正确绘制的渐变和角,而其他3个按钮没有。 我已经确认,向此按钮添加一个插座可以使其与其他按钮一样工作,从其他3个按钮中的任何一个删除插座都可以使其正确绘制 .

    仅供参考

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

    class MyViewController: UIViewController { 公告

    此外,我没有忘记在interface builder中为每个按钮设置自定义类。

    以下是button类:

    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 回复  |  直到 6 年前
        1
  •  2
  •   Community raghavsood33    4 年前

    一些想法:

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

      在您的代码片段中 绘制(\u:) 正在呼叫 setupView ,然后调用 setNeedsDisplay (理论上可以触发 绘制(\u:) 再次调用)。我建议预约 绘制(\u:) 仅用于需要手动绘制的对象。(现在,您可能可以完全消除这种方法,因为这里没有任何东西可以绘制任何东西。)

    2. 对于子视图(其框架等)的任何手动配置,应移动到 layoutSubviews ,每当操作系统确定子视图可能需要 frame 值已调整。

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

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

    我必须承认,我不知道为什么在可设计视图中添加出口会影响视图的渲染方式。我知道,当我做上述事情时,我无法再现你的问题。

    不管怎样,我调整了你的 ActionButton 就像这样,这似乎对我有用:

    @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
        }
    
    }
    
        2
  •  0
  •   Busman    6 年前

    我接受了Rob的答案,因为这对其他人来说可能有用得多,但我发现了我的问题,很可能有人也有类似的问题。

    显然,在我为放置这些按钮的视图控制器编写的代码中,我有一个忘记了的函数,该函数可以启用/禁用这些自定义按钮,以及设置它们的背景色(为纯色)。这就是为什么那些按钮看起来不对的原因。第四个按钮从未因该功能而发生突变。这并不是因为其他3个有一个插座,而是当我移除插座时,我注释掉了对该函数的调用。

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

    -我有一个非常草率的UIButton实现,它重写并错误地调用UI函数,这会导致旋转时冻结等问题。Rob(公认的)回答中也提到了这些问题。

    -界面生成器中的“刷新所有视图”挂起在“签名产品”上,这是由调用 self.titleLabel?.font . 根据 some answers 在Apple开发者论坛上,在 layoutSubViews() 太糟糕了!