在 Swift 中的按钮上设置背景渐变
Posted
技术标签:
【中文标题】在 Swift 中的按钮上设置背景渐变【英文标题】:Set Background Gradient on Button in Swift 【发布时间】:2016-10-20 13:32:20 【问题描述】:我不知道如何在按钮上设置背景渐变(不使背景渐变成为图像)。这与 android 大不相同。
这是一个我必须定义可返回渐变方案的类:
import UIKit
extension CAGradientLayer
func backgroundGradientColor() -> CAGradientLayer
let topColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(51/255.0), alpha: 1)
let bottomColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(255/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
我可以使用它来设置整个视图的背景:
class ViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
let background = CAGradientLayer().backgroundGradientColor()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
//...
但是如何访问按钮的视图并插入子层或类似的东西?
【问题讨论】:
这里对 OP 问题的简单回答是 您不能在视图控制器中执行此操作 - 您只需将视图子类化。一两行代码。 【参考方案1】:您的代码运行良好。您只需要记住每次都设置渐变的框架。最好让渐变类别也为您设置视图的框架。
这样你就不会忘记,而且效果很好。
import UIKit
extension UIView
func applyGradient(colours: [UIColor]) -> CAGradientLayer
return self.applyGradient(colours: colours, locations: nil)
func applyGradient(colours: [UIColor], locations: [NSNumber]?) -> CAGradientLayer
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map $0.cgColor
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
return gradient
class ViewController: UIViewController
@IBOutlet weak var btn: UIButton!
override func viewDidLoad()
super.viewDidLoad()
self.btn.applyGradient(colours: [.yellow, .blue])
self.view.applyGradient(colours: [.yellow, .blue, .red], locations: [0.0, 0.5, 1.0])
按钮是视图。将渐变应用到它的方式与将它应用到任何其他视图的方式相同。
图片校样:
视频证明: https://i.imgur.com/ssDTqPu.mp4
【讨论】:
我不同意最后一句话。虽然按钮是视图,但按钮背景与视图背景不同。按钮背景可以根据按钮状态而改变。当添加渐变作为图层时,我们失去了这种效果。理想的解决方案是将渐变层捕获到图像中,并将图像设置为按钮背景图像。另一个问题是图层边界应该总是在按钮框架发生变化时更新,即从layoutSubviews
开始。
为了记录,我确切地知道你在说什么,但它不适用于这篇基于故事板约束的帖子(不是代码内运行时约束或框架)。由于我已经发布了代码工作的视频证明并且它满足了 OP 的需求并且在 3 年多前被接受,目前有 48 人同意,我将删除我的 cmets 以清理这个线程并忘记我已经阅读了我已经阅读的内容阅读..
为了简化讨论,在 ios 中添加 CALayer 时,必须在 layoutSubviews
中设置框架(实际上这就是 layoutSubviews
存在的原因。)在示例中尝试更改 视图的形状或大小,或者,非常简单,旋转手机
@Fattie;如果未设置视图/图层的框架,并且我发布的视频似乎显示此工作没有设置框架,iOS 将不会崩溃。如果它崩溃了,你有一个不同的问题。这是我自己为按钮位置设置动画的另一个视频:i.imgur.com/vQfsvtO.mp4 没有崩溃就可以了。更改图层或视图的位置不会使其约束或渐变无效。仅更改 size 需要layoutSubviews
或要求您完全按照我所说的去做:You just have to remember to set the gradient's frame every time
..
i.imgur.com/hFaQM3C.mp4 第三个视频显示不需要覆盖布局子视图来动画渐变的大小和位置和旋转。对于某些人来说覆盖它可能很方便(在子类/子类中)但这绝对没有必要,尤其是当您无法对视图进行子类化时。没有人会为了创建渐变和覆盖子类中的 layoutSubviews 而子类化/继承按钮。话虽如此,我将按照 4 年前的方式保留我的答案,因为它仍然有效并且直到今天仍然有效。【参考方案2】:
您可以在下面找到 Swift3(以及 Swift4)和一些扩展(方向助手)的解决方案:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint : CGPoint
return points.startPoint
var endPoint : CGPoint
return points.endPoint
var points : GradientPoints
switch self
case .topRightBottomLeft:
return (CGPoint(x: 0.0,y: 1.0), CGPoint(x: 1.0,y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 1,y: 1))
case .horizontal:
return (CGPoint(x: 0.0,y: 0.5), CGPoint(x: 1.0,y: 0.5))
case .vertical:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 0.0,y: 1.0))
extension UIView
func applyGradient(with colours: [UIColor], locations: [NSNumber]? = nil)
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map $0.cgColor
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
func applyGradient(with colours: [UIColor], gradient orientation: GradientOrientation)
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map $0.cgColor
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
【讨论】:
不幸的是,由于另一个答案中详细解释的原因,这完全是错误的。在 iOS 中,您只能 > 通过创建视图的子类来添加 CALayer。 (幸运的是,这完全是微不足道的——一两行代码。)上面的两个扩展根本不起作用,因为它们不以任何方式调整层的大小。【参考方案3】:@Zeb 的答案很棒,但只是为了清理它并让它更快一点。 计算的只读属性应该避免使用 get 并且返回 Void 是多余的:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint
return points.startPoint
var endPoint: CGPoint
return points.endPoint
var points: GradientPoints
switch self
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
extension UIView
func applyGradient(withColours colours: [UIColor], locations: [NSNumber]? = nil)
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map $0.cgColor
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation)
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map $0.cgColor
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
【讨论】:
这很好用,我尝试设置 UIButtoncornerRadius,但除非我删除渐变,否则它不会生效,有没有办法解决这个问题? 你尝试设置masksToBounds = false 设置 clipsToBounds = true 不幸的是,这是完全错误的。这是 iOS 工程的绝对基础,当你添加一个层时,你必须在layoutSubviews
中对其进行整形(事实上这就是 layoutSubviews
存在的原因。)
是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案4】:
我已经尝试了所有这些这是我在 viewdidload 中的按钮初始化
let button = UIButton()
button.setTitle("Alper", for: .normal)
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
view.addSubview(button)
button.anchor(top: nil, left: nil, bottom: logo.topAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 50, width: 100)
let gradientx = CAGradientLayer()
gradientx.colors = [UIColor.blue,UIColor.red]
gradientx.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientx.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientx.frame = button.bounds
button.layer.insertSublayer(gradientx, at: 0)
anchor 是一个扩展,所以这是不相关的梯度。
【讨论】:
不幸的是,这是完全错误的。这是 iOS 工程的绝对基础,当你添加一个层时,你必须在layoutSubviews
中对其进行整形(事实上这就是 layoutSubviews
存在的原因。)
是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案5】:
对于斯威夫特
extension UIViewController
func makeGradientColor(`for` object : AnyObject , startPoint : CGPoint , endPoint : CGPoint) -> CAGradientLayer
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [(UIColor.red.cgColor), (UIColor.yellow.cgColor)]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = CGRect(x: 0.0, y: 0.0, width: object.bounds.size.width, height: object.bounds.size.height)
return gradient
如何使用?
if let layers = btn.layer.sublayers
for layer in layers
if layer.isKind(of: CAGradientLayer.self)
layer.removeFromSuperlayer()
let start : CGPoint = CGPoint(x: 0.0, y: 0.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)
let gradient: CAGradientLayer = self.makeGradientColor(for: cell.bgView, startPoint: start, endPoint: end)
btn.layer.insertSublayer(gradient, at: 0)
【讨论】:
不幸的是,这是完全错误的。这是 iOS 工程的绝对基础,当你添加一个层时,你必须在layoutSubviews
中对其进行整形(实际上这就是layoutSubviews
存在的原因。)
是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案6】:
试试这对我有用,
let button = UIButton(frame: CGRect(x: 60, y: 150, width: 200, height: 60))
button.setTitle("Email", for: .normal)
button.backgroundColor = .red
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
// Apply Gradient Color
let gradientLayer:CAGradientLayer = CAGradientLayer()
gradientLayer.frame.size = button.frame.size
gradientLayer.colors =
[UIColor.white.cgColor,UIColor.green.withAlphaComponent(1).cgColor]
//Use diffrent colors
button.layer.addSublayer(gradientLayer)
self.view.addSubview(button)
可以添加渐变色的起点和终点。
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
更多详情请参考CAGradientLayer doc
【讨论】:
不幸的是,这是完全错误的。这是 iOS 工程的绝对基础,当你添加一个层时,你必须在layoutSubviews
中对其进行整形(实际上这就是layoutSubviews
存在的原因。)
是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案7】:
那里已经有很多答案我想添加我为实现这一目标所做的事情。我使用这个自定义按钮 GradientButton
import Foundation
import UIKit
class GradientButton: UIButton
let gradientColors : [UIColor]
let startPoint : CGPoint
let endPoint : CGPoint
required init(gradientColors: [UIColor] = [UIColor.red, UIColor.blue],
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5))
self.gradientColors = gradientColors
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func layoutSubviews()
super.layoutSubviews()
let halfOfButtonHeight = layer.frame.height / 2
contentEdgeInsets = UIEdgeInsets(top: 10, left: halfOfButtonHeight, bottom: 10, right: halfOfButtonHeight)
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
backgroundColor = UIColor.clear
// setup gradient
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = gradientColors.map $0.cgColor
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.cornerRadius = 4
// replace gradient as needed
if let oldGradient = layer.sublayers?[0] as? CAGradientLayer
layer.replaceSublayer(oldGradient, with: gradient)
else
layer.insertSublayer(gradient, below: nil)
// setup shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: halfOfButtonHeight).cgPath
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowOpacity = 0.85
layer.shadowRadius = 4.0
override var isHighlighted: Bool
didSet
let newOpacity : Float = isHighlighted ? 0.6 : 0.85
let newRadius : CGFloat = isHighlighted ? 6.0 : 4.0
let shadowOpacityAnimation = CABasicAnimation()
shadowOpacityAnimation.keyPath = "shadowOpacity"
shadowOpacityAnimation.fromValue = layer.shadowOpacity
shadowOpacityAnimation.toValue = newOpacity
shadowOpacityAnimation.duration = 0.1
let shadowRadiusAnimation = CABasicAnimation()
shadowRadiusAnimation.keyPath = "shadowRadius"
shadowRadiusAnimation.fromValue = layer.shadowRadius
shadowRadiusAnimation.toValue = newRadius
shadowRadiusAnimation.duration = 0.1
layer.add(shadowOpacityAnimation, forKey: "shadowOpacity")
layer.add(shadowRadiusAnimation, forKey: "shadowRadius")
layer.shadowOpacity = newOpacity
layer.shadowRadius = newRadius
let xScale : CGFloat = isHighlighted ? 1.025 : 1.0
let yScale : CGFloat = isHighlighted ? 1.05 : 1.0
UIView.animate(withDuration: 0.1)
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
你可以像这样制作 GradientButton 实例。
let button = GradientButton.init(gradientColors:[UIColor.black, UIColor.white], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: 1))
【讨论】:
很遗憾,这是完全错误的,您肯定不会在每次应用布局时都创建图层。 是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案8】: class ButtonGradient : UIButton
override func layoutSubviews()
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPoint(x: 0, y: 0)
// layer.cornerRadius = CGFloat(frame.width / 20)
let color0 = UIColor(red:255/255, green:122/255, blue:0/255, alpha:1.0).cgColor
let color1 = UIColor(red:255/255, green:176/255, blue: 0/255, alpha:1.0).cgColor
let color2 = UIColor(red:250/255, green:98/255, blue: 44/255, alpha:1.0).cgColor
layer.locations = [0.5, 1.0]
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 0.5, y: 0.5)
layer.colors = [color2,color0,color1]
self.layer.insertSublayer(layer, at: 0)
之后直接将“ButtonGredient”类分配给 Storyboard 中的特定按钮。
【讨论】:
这是大错特错。您正在制作 100 和数千个渐变层 - 每次布局应用程序时都会创建一个新层! 是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案9】:在这里,我取了一个 UIView 并在其中添加了按钮。
@IBOutlet weak var btnCenter: UIButton!
@IBOutlet weak var viewCenter: UIView!
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.yello.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewCenter.bounds
// add the gradient layer to the views layer for rendering
viewCenter.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewCenter.mask = btnCenter
//Set corner Radius and border Width of button
btnCenter.layer.cornerRadius = btnCenter.frame.size.height / 2
btnCenter.layer.borderWidth = 5.0
【讨论】:
不幸的是,这是完全错误的。这是 iOS 工程的绝对基础,当你添加一个层时,你必须在layoutSubviews
中对其进行整形(事实上这就是 layoutSubviews
存在的原因。)
是的。在添加新的之前,应该更新答案以删除现有的 gradientLayer。在 insertSublayer 之前添加以下行。 layer.name = "gradientLayer" self.layer.sublayers.filter $0.name == layer.name .first?.removeFromSuperLayer()【参考方案10】:
就这么简单:
import UIKit
class ActualGradientButton: UIButton
override func layoutSubviews()
super.layoutSubviews()
gradientLayer.frame = bounds
private lazy var gradientLayer: CAGradientLayer =
let l = CAGradientLayer()
l.frame = self.bounds
l.colors = [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor]
l.startPoint = CGPoint(x: 0, y: 0.5)
l.endPoint = CGPoint(x: 1, y: 0.5)
l.cornerRadius = 16
layer.insertSublayer(l, at: 0)
return l
()
【讨论】:
特别适合使用AutoLayout时的情况 @black_pearl - 无论如何,是的,它与 AutoLayout 完美结合。顺便说一句,这个答案的反对票和“删除请求”(???)完全奇怪:)【参考方案11】:如果您想要按钮上的渐变背景,而不是将渐变添加为子层并在layoutSubviews
中更改其frame
,我将改为将按钮的layerClass
指定为CAGradientLayer
,所以主层是一个渐变:
@IBDesignable
public class GradientButton: UIButton
public override class var layerClass: AnyClass CAGradientLayer.self
private var gradientLayer: CAGradientLayer layer as! CAGradientLayer
@IBInspectable public var startColor: UIColor = .white didSet updateColors()
@IBInspectable public var endColor: UIColor = .red didSet updateColors()
// expose startPoint and endPoint to IB
@IBInspectable public var startPoint: CGPoint
get gradientLayer.startPoint
set gradientLayer.startPoint = newValue
@IBInspectable public var endPoint: CGPoint
get gradientLayer.endPoint
set gradientLayer.endPoint = newValue
// while we're at it, let's expose a few more layer properties so we can easily adjust them in IB
@IBInspectable public var cornerRadius: CGFloat
get layer.cornerRadius
set layer.cornerRadius = newValue
@IBInspectable public var borderWidth: CGFloat
get layer.borderWidth
set layer.borderWidth = newValue
@IBInspectable public var borderColor: UIColor?
get layer.borderColor.flatMap UIColor(cgColor: $0)
set layer.borderColor = newValue?.cgColor
// init methods
public override init(frame: CGRect = .zero)
super.init(frame: frame)
updateColors()
required init?(coder: NSCoder)
super.init(coder: coder)
updateColors()
private extension GradientButton
func updateColors()
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
通过设置layerClass
,它只会使主图层为渐变,它会自动为您调整到按钮的bounds
。这样做的好处是,如果您为按钮大小的变化设置动画(例如,在旋转事件或其他情况下),渐变也将正确设置动画。
而且,这不是必需的,但将此类设为@IBDesignable
可能会很方便,因此可以在 IB 中设置它的属性,它会在 storyboard/NIB 中正确呈现,而无需在视图控制器。例如,我可以在 IB 中自定义角、边框、渐变颜色和方向:
【讨论】:
【参考方案12】:我修改了this great answer,通过添加init
颜色、半径和渐变方向参数来提高按钮的可重用性。
我还添加了updateGradientColors
方法,因为如果您想在某些时候更改渐变颜色,它可能会很有用。
class GradientButton: UIButton
private let colors: [UIColor]
private let cornerRadius: CGFloat
private let startPoint: CGPoint
private let endPoint: CGPoint
required init?(coder: NSCoder) fatalError("init(coder:) has not been implemented")
init(colors: [UIColor],
cornerRadius: CGFloat = 10,
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5))
self.colors = colors
self.cornerRadius = cornerRadius
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
override func layoutSubviews()
super.layoutSubviews()
gradientLayer.frame = bounds
private lazy var gradientLayer: CAGradientLayer =
let gl = CAGradientLayer()
gl.frame = self.bounds
gl.colors = colors.map $0.cgColor
gl.startPoint = startPoint
gl.endPoint = endPoint
gl.cornerRadius = cornerRadius
layer.insertSublayer(gl, at: 0)
return gl
()
func updateGradientColors(_ colors: [UIColor])
gradientLayer.colors = colors.map $0.cgColor
【讨论】:
【参考方案13】:有一些方法可以在不创建子层的情况下使用初始层。
import UIKit
@IBDesignable class GradientButton: UIButton
@IBInspectable var startColor: UIColor = UIColor.white
@IBInspectable var endColor: UIColor = UIColor.white
@IBInspectable var cornerRadius = CGFloat(5.0)
override class var layerClass: AnyClass
return CAGradientLayer.self
override func layoutSubviews()
super.layoutSubviews()
//This is an advanced gradient we do not use for now
// (layer as! CAGradientLayer).startPoint = CGPoint(x: 0, y: 0)
// (layer as! CAGradientLayer).endPoint = CGPoint(x: 1, y: 1)
// (layer as! CAGradientLayer).locations = [0,1]
// Simple gradient
(layer as! CAGradientLayer).colors = [startColor.cgColor, endColor.cgColor]
layer.cornerRadius = cornerRadius
【讨论】:
以上是关于在 Swift 中的按钮上设置背景渐变的主要内容,如果未能解决你的问题,请参考以下文章