Swift 3 - 自定义按钮(图像和文本)只有在没有插座时才能正确绘制?
Swift 3 - Custom button (image and text) only draws correctly if there's no outlet?
编辑:虽然我接受了一个帮助我在运行时正确绘制按钮的答案,但我还有其他问题。我怀疑,其根源是 为什么给我的自定义按钮一个插座会干扰它的绘制方式。我仍然需要知道这是为什么。(请参阅下面的回答)
我有自己的按钮 class,它扩展了 UIButton(见下文)并有几个 IBInspectable 属性,如边框 width/color、圆角半径,甚至 start/end 渐变背景颜色.我还使用这些属性以编程方式设置图像和标题的插图,这样我就可以考虑各种屏幕尺寸。
以前我有一个问题,如果我在故事板中更改 "View as" 设备,假设从 iPhone SE 到 iPhone 7,然后刷新视图和 运行 在物理 iPhone SE 上,它将使用 iPhone 7 的插图进行部署,而不是使用物理设备自己的屏幕尺寸计算插图。但是,我 几乎 通过在自定义按钮 class.
中覆盖 draw
解决了这个问题
我现在的问题是重写的 draw
方法仅被调用(或似乎仅在以下情况下有效)按钮没有 ViewController
class 放在.
例如:
*img为占位符;插图适合真实的 imgs。
bottom-right 按钮的渐变和角绘制正确,而其他 3 个按钮则没有。 我已经确认向该按钮添加一个插座会使它的行为与其他按钮一样,并且从其他 3 个中移除任何一个插座都会使其正确绘制。
供参考,网点刚好
@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
@IBOutlet weak var button3: UIButton!
直接在class MyViewController: UIViewController {
声明下。
此外,我没有忘记在界面生成器中为每个按钮设置自定义 Class。
这是按钮class:
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
}
}
请忽略我处理插图、字体大小等的奇怪方式。
有谁知道为什么为按钮指定 outlet 会导致它无法正确绘制图层?
几个想法:
draw(_:)
方法用于描边路径、绘制图像等,用于渲染给定时刻的视图。您不应该更新图层、UIKit 子视图等。
在您的代码段中,您的 draw(_:)
正在调用 setupView
,然后调用 setNeedsDisplay
(理论上可以触发 draw(_:)
再次被调用)。我建议只为那些需要手动绘制的东西保留 draw(_:)
。 (就像现在一样,您可能可以完全消除此方法,因为这里没有任何东西正在绘制任何东西。)
对于子视图(它们的框架等)的任何手动配置,都应移至 layoutSubviews
,只要 OS 确定子视图可能会被调用需要调整它们的 frame
值。
如果您对可设计视图有任何疑问,值得确认您将它们放在单独的目标中。它最大限度地减少了在 IB 中渲染时需要重新编译的内容。它还可以防止主要目标中的问题影响在 IB 中呈现这些可设计对象的能力。 (当 Apple 引入可设计视图时,它们非常具体,应该将它们放在一个单独的目标中。)
我在 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
}
}
我接受了 Rob 的回答,因为它更有可能对其他人有用,但我发现我的问题具体是什么,如果有人有类似的机会问题。
显然在我放置这些按钮的视图控制器的代码中,我忘记了 enables/disables 那些自定义按钮的功能,以及设置它们的背景颜色(纯色) .这就是为什么这些按钮看起来不对的原因。第 4 个按钮从未受到此功能的影响。这不是其他 3 个有插座的事实,而是当我移除插座时我注释掉了对该函数的调用。
然而,这个问题解决了其他问题!
-我有一个非常草率的 UIButton 实现,它覆盖并调用了 UI 功能不正确,导致旋转冻结等问题。 Rob(已接受)的回答也解决了这些问题。
Interface Builder 中的 -"Refresh All Views" 挂在 "Signing product" 上,这是由调用 self.titleLabel?.font
引起的。根据 Apple 开发者论坛上的 some answers,在 layoutSubViews()
中这样做很糟糕!
编辑:虽然我接受了一个帮助我在运行时正确绘制按钮的答案,但我还有其他问题。我怀疑,其根源是 为什么给我的自定义按钮一个插座会干扰它的绘制方式。我仍然需要知道这是为什么。(请参阅下面的回答)
我有自己的按钮 class,它扩展了 UIButton(见下文)并有几个 IBInspectable 属性,如边框 width/color、圆角半径,甚至 start/end 渐变背景颜色.我还使用这些属性以编程方式设置图像和标题的插图,这样我就可以考虑各种屏幕尺寸。
以前我有一个问题,如果我在故事板中更改 "View as" 设备,假设从 iPhone SE 到 iPhone 7,然后刷新视图和 运行 在物理 iPhone SE 上,它将使用 iPhone 7 的插图进行部署,而不是使用物理设备自己的屏幕尺寸计算插图。但是,我 几乎 通过在自定义按钮 class.
中覆盖draw
解决了这个问题
我现在的问题是重写的 draw
方法仅被调用(或似乎仅在以下情况下有效)按钮没有 ViewController
class 放在.
例如:
*img为占位符;插图适合真实的 imgs。
bottom-right 按钮的渐变和角绘制正确,而其他 3 个按钮则没有。 我已经确认向该按钮添加一个插座会使它的行为与其他按钮一样,并且从其他 3 个中移除任何一个插座都会使其正确绘制。
供参考,网点刚好
@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
@IBOutlet weak var button3: UIButton!
直接在class MyViewController: UIViewController {
声明下。
此外,我没有忘记在界面生成器中为每个按钮设置自定义 Class。
这是按钮class:
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
}
}
请忽略我处理插图、字体大小等的奇怪方式。
有谁知道为什么为按钮指定 outlet 会导致它无法正确绘制图层?
几个想法:
draw(_:)
方法用于描边路径、绘制图像等,用于渲染给定时刻的视图。您不应该更新图层、UIKit 子视图等。在您的代码段中,您的
draw(_:)
正在调用setupView
,然后调用setNeedsDisplay
(理论上可以触发draw(_:)
再次被调用)。我建议只为那些需要手动绘制的东西保留draw(_:)
。 (就像现在一样,您可能可以完全消除此方法,因为这里没有任何东西正在绘制任何东西。)对于子视图(它们的框架等)的任何手动配置,都应移至
layoutSubviews
,只要 OS 确定子视图可能会被调用需要调整它们的frame
值。如果您对可设计视图有任何疑问,值得确认您将它们放在单独的目标中。它最大限度地减少了在 IB 中渲染时需要重新编译的内容。它还可以防止主要目标中的问题影响在 IB 中呈现这些可设计对象的能力。 (当 Apple 引入可设计视图时,它们非常具体,应该将它们放在一个单独的目标中。)
我在 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
}
}
我接受了 Rob 的回答,因为它更有可能对其他人有用,但我发现我的问题具体是什么,如果有人有类似的机会问题。
显然在我放置这些按钮的视图控制器的代码中,我忘记了 enables/disables 那些自定义按钮的功能,以及设置它们的背景颜色(纯色) .这就是为什么这些按钮看起来不对的原因。第 4 个按钮从未受到此功能的影响。这不是其他 3 个有插座的事实,而是当我移除插座时我注释掉了对该函数的调用。
然而,这个问题解决了其他问题!
-我有一个非常草率的 UIButton 实现,它覆盖并调用了 UI 功能不正确,导致旋转冻结等问题。 Rob(已接受)的回答也解决了这些问题。
Interface Builder 中的-"Refresh All Views" 挂在 "Signing product" 上,这是由调用 self.titleLabel?.font
引起的。根据 Apple 开发者论坛上的 some answers,在 layoutSubViews()
中这样做很糟糕!