如何在iOS Swift应用程序中将渐变应用于背景视图

231

我正在尝试将渐变作为视图的背景颜色应用于一个视图(storyboard 的主视图)。代码可以运行,但是没有任何变化。我正在使用 xCode Beta 2 和 Swift。

以下是代码:

class Colors {
  let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0)
  let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0)

  let gl: CAGradientLayer

  init() {
    gl = CAGradientLayer()
    gl.colors = [ colorTop, colorBottom]
    gl.locations = [ 0.0, 1.0]
  }
}

然后在视图控制器中:

  let colors = Colors()

  func refresh() {
        view.backgroundColor = UIColor.clearColor()
        var backgroundLayer = colors.gl
        backgroundLayer.frame = view.frame
        view.layer.insertSublayer(backgroundLayer, atIndex: 0)
      }
    }
  }

1
我发布了一个组件,它使得使用变得很容易,你可以通过 CocoaPods 使用它。我建议使用它,因为它非常简单,并且可以通过 XCode 上的界面构建器进行设置。详情请见:https://github.com/heuristisk/hkGraddiant - Anderson Santos Gusmão
你正在使用UIColor元素在CAGradientLayer的属性颜色上,但是你必须使用CGColor! - Rodrigo Fava
30个回答

251

Xcode 11 • Swift 5.1


您可以按照以下方式设计自己的渐变视图:

@IBDesignable
public class Gradient: UIView {
    @IBInspectable var startColor:   UIColor = .black { didSet { updateColors() }}
    @IBInspectable var endColor:     UIColor = .white { didSet { updateColors() }}
    @IBInspectable var startLocation: Double =   0.05 { didSet { updateLocations() }}
    @IBInspectable var endLocation:   Double =   0.95 { didSet { updateLocations() }}
    @IBInspectable var horizontalMode:  Bool =  false { didSet { updatePoints() }}
    @IBInspectable var diagonalMode:    Bool =  false { didSet { updatePoints() }}

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

    var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }

    func updatePoints() {
        if horizontalMode {
            gradientLayer.startPoint = diagonalMode ? .init(x: 1, y: 0) : .init(x: 0, y: 0.5)
            gradientLayer.endPoint   = diagonalMode ? .init(x: 0, y: 1) : .init(x: 1, y: 0.5)
        } else {
            gradientLayer.startPoint = diagonalMode ? .init(x: 0, y: 0) : .init(x: 0.5, y: 0)
            gradientLayer.endPoint   = diagonalMode ? .init(x: 1, y: 1) : .init(x: 0.5, y: 1)
        }
    }
    func updateLocations() {
        gradientLayer.locations = [startLocation as NSNumber, endLocation as NSNumber]
    }
    func updateColors() {
        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
    }
    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        updatePoints()
        updateLocations()
        updateColors()
    }

}

在这里输入图片描述


1
不要忘记导入UIKit。 - djdance
你能解释一下这行代码吗:override class var layerClass: AnyClass { return CAGradientLayer.self } - Mostafa
1
https://developer.apple.com/documentation/uikit/uiview/1622626-layerclass - Leo Dabus
最大可适用的位置/位置/渐变点数量是多少?似乎超过某个数量,iOS就无法渲染渐变。页面为空。 - Arun Prasad
1
这是一种简单而美好的方式,你做得很好。谢谢你,点赞。 - mAc

175

您提供给渐变的颜色必须是CGColor类型。因此,请将您的CGColor数组设置为gl.colors

正确的代码是:

class Colors {
    var gl:CAGradientLayer!

    init() {
        let colorTop = UIColor(red: 192.0 / 255.0, green: 38.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0).cgColor
        let colorBottom = UIColor(red: 35.0 / 255.0, green: 2.0 / 255.0, blue: 2.0 / 255.0, alpha: 1.0).cgColor

        self.gl = CAGradientLayer()
        self.gl.colors = [colorTop, colorBottom]
        self.gl.locations = [0.0, 1.0]
    }
}

21
这是关键:gl.frame = view.bounds; - justingordon
2
当我给.colors属性赋值时,出现了这个错误fatal error: array element cannot be bridged to Objective-C。可能出了什么问题? - Tricertops
3
我已经修复了它。这是 Swift 的一个 bug,只需使用显式类型 [AnyObject] 将数组存储在变量中,然后再将其赋值给 .colors 属性即可。 - Tricertops
1
让 c: Array <AnyObject> = [colorTop, colorBottom] - JP_
7
对于 Swift 1.0,语法将是 let c: [AnyObject] = [colorTop, colorBottom] - Chris
显示剩余3条评论

119

仅修改上面提到的答案。

输入图像描述

func setGradientBackground() {
    let colorTop =  UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
    let colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
                
    let gradientLayer = CAGradientLayer()
    gradientLayer.colors = [colorTop, colorBottom]
    gradientLayer.locations = [0.0, 1.0]
    gradientLayer.frame = self.view.bounds
            
    self.view.layer.insertSublayer(gradientLayer, at:0)
}

然后在 viewWillAppear 方法中调用此方法。

override func viewWillAppear(_ animated: Bool) {
    setGradientBackground()
    super.viewWillAppear(animated)
}

在此输入图片描述


1
这会在现有元素上覆盖一层,所以我什么都看不到。有什么建议吗? - JamesG
31
请将「replace self.view.layer.addSublayer(gradientLayer) with self.view.layer.insertSublayer(gradientLayer, at: 0), this will put the layer "below" all others」翻译成通俗易懂的中文,不要改变原来的意思。将代码中的self.view.layer.addSublayer(gradientLayer)替换为self.view.layer.insertSublayer(gradientLayer, at: 0),这样会将该图层放在所有其它图层之下。 - WMR
@TylerRutt 只需为您的图层命名,然后无论其位置如何,都可以对其进行过滤。 - Leo Dabus
1
我将它移到了 viewDidLayoutSubviews 中,然后它就可以工作了 :) 也许 insertSublayer 会改变 UIView 的边界。 - Zhou Haibo
请注意,CAGradientLayer属性colors是[Any]?,但元素必须是CGColor而不是UIColor。我犯了这个错误好几次,结果什么都没有发生。 - Rodrigo Fava
显示剩余4条评论

98

如果你需要改变渐变的方向,你必须使用 startPoint 和 endPoint。

let gradient: CAGradientLayer = CAGradientLayer()

gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
gradient.frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: self.view.frame.size.height)

self.view.layer.insertSublayer(gradient, at: 0)

6
您可能会遇到的一个问题是,当您添加子层时,它可能会覆盖所有其他项目,例如标签、图像等。为了克服这个问题,请创建另一个视图,并将其设置在所有内容下方,并将其约束设置为您想要应用渐变的容器的约束。然后将渐变设置为应用于此视图。子视图将被插入到此图层中,而不会覆盖任何其他内容。 - Micah Montoya

25

我有这些扩展:

@IBDesignable class GradientView: UIView {
    @IBInspectable var firstColor: UIColor = UIColor.red
    @IBInspectable var secondColor: UIColor = UIColor.green

    @IBInspectable var vertical: Bool = true

    lazy var gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [firstColor.cgColor, secondColor.cgColor]
        layer.startPoint = CGPoint.zero
        return layer
    }()

    //MARK: -

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        applyGradient()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        applyGradient()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        applyGradient()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateGradientFrame()
    }

    //MARK: -

    func applyGradient() {
        updateGradientDirection()
        layer.sublayers = [gradientLayer]
    }

    func updateGradientFrame() {
        gradientLayer.frame = bounds
    }

    func updateGradientDirection() {
        gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
    }
}

@IBDesignable class ThreeColorsGradientView: UIView {
    @IBInspectable var firstColor: UIColor = UIColor.red
    @IBInspectable var secondColor: UIColor = UIColor.green
    @IBInspectable var thirdColor: UIColor = UIColor.blue

    @IBInspectable var vertical: Bool = true {
        didSet {
            updateGradientDirection()
        }
    }

    lazy var gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [firstColor.cgColor, secondColor.cgColor, thirdColor.cgColor]
        layer.startPoint = CGPoint.zero
        return layer
    }()

    //MARK: -

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        applyGradient()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        applyGradient()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        applyGradient()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateGradientFrame()
    }

    //MARK: -

    func applyGradient() {
        updateGradientDirection()
        layer.sublayers = [gradientLayer]
    }

    func updateGradientFrame() {
        gradientLayer.frame = bounds
    }

    func updateGradientDirection() {
        gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
    }
}

@IBDesignable class RadialGradientView: UIView {

    @IBInspectable var outsideColor: UIColor = UIColor.red
    @IBInspectable var insideColor: UIColor = UIColor.green

    override func awakeFromNib() {
        super.awakeFromNib()

        applyGradient()
    }

    func applyGradient() {
        let colors = [insideColor.cgColor, outsideColor.cgColor] as CFArray
        let endRadius = sqrt(pow(frame.width/2, 2) + pow(frame.height/2, 2))
        let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
        let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
        let context = UIGraphicsGetCurrentContext()

        context?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        #if TARGET_INTERFACE_BUILDER
            applyGradient()
        #endif
    }
}

使用方法:

输入图片描述

输入图片描述

输入图片描述


最好不要在drawRect中添加图层,而是在设置时间添加。 - Fattie
1
将此添加到我的 VC 视图块中会遮挡一切。所有元素都会隐藏在渐变下面。有什么解决方法? - Aakash Dave
1
self.layer.addSublayer(layer) 更改为 self.layer.insertSublayer(layer, at: 0) 似乎可以防止渐变遮盖界面构建器中的所有内容(至少在我的一个测试中)。 - S.Walker

24

尝试在 Swift3 中使用以下代码:

 func addGradient(){

    let gradient:CAGradientLayer = CAGradientLayer()
    gradient.frame.size = self.viewThatHoldsGradient.frame.size
    gradient.colors = [UIColor.white.cgColor,UIColor.white.withAlphaComponent(0).cgColor] //Or any colors
    self.viewThatHoldsGradient.layer.addSublayer(gradient)

}

6
渐变的起始点为CGPoint(x:0.0,y:1.0),终止点为CGPoint(x:1.0,y:1.0),可根据需要改变渐变位置。 - Pan Mluvčí
colorWithAlphaComponent已更名为withAlphaComponent。 - etayluz

22

我编写了一个UIView扩展,可以将基本渐变应用于任何视图

extension UIView {
    func layerGradient() {
        let layer : CAGradientLayer = CAGradientLayer()
        layer.frame.size = self.frame.size
        layer.frame.origin = CGPointMake(0.0,0.0)
        layer.cornerRadius = CGFloat(frame.width / 20)

        let color0 = UIColor(red:250.0/255, green:250.0/255, blue:250.0/255, alpha:0.5).CGColor
        let color1 = UIColor(red:200.0/255, green:200.0/255, blue: 200.0/255, alpha:0.1).CGColor
        let color2 = UIColor(red:150.0/255, green:150.0/255, blue: 150.0/255, alpha:0.1).CGColor
        let color3 = UIColor(red:100.0/255, green:100.0/255, blue: 100.0/255, alpha:0.1).CGColor
        let color4 = UIColor(red:50.0/255, green:50.0/255, blue:50.0/255, alpha:0.1).CGColor
        let color5 = UIColor(red:0.0/255, green:0.0/255, blue:0.0/255, alpha:0.1).CGColor
        let color6 = UIColor(red:150.0/255, green:150.0/255, blue:150.0/255, alpha:0.1).CGColor

        layer.colors = [color0,color1,color2,color3,color4,color5,color6]
        self.layer.insertSublayer(layer, atIndex: 0)
    }
}       

8
我可能有误解,但是最好让用户将颜色数组传递给该函数,而不是在其中硬编码它们(除非您始终希望应用相同的渐变)。 - user470763
4
为了简洁起见,可以用 CGPointZero 替换 CGPointMake(0.0, 0.0) - JSmyth

18

试试这个,对我有效。

  var gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 35))
  let gradientLayer:CAGradientLayer = CAGradientLayer()
  gradientLayer.frame.size = self.gradientView.frame.size
  gradientLayer.colors = 
  [UIColor.white.cgColor,UIColor.red.withAlphaComponent(1).cgColor] 
  //Use diffrent colors
  gradientView.layer.addSublayer(gradientLayer)

enter image description here

您可以添加渐变颜色的起始点和结束点。

    gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
    gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)

这里输入图像描述

如需更详细的说明,请参阅最佳答案或者你可以查看苹果的CAGradientLayer文档

希望能对某些人有所帮助。


使用以下 startPointendPoint 进行从左到右的渐变: gradient.startPoint = CGPoint(x: 0.0, y: 0.5) gradient.endPoint = CGPoint(x: 1.0, y: 0.5) 以及从上到下的渐变: gradient.startPoint = CGPoint(x: 0.5, y: 0.0) gradient.endPoint = CGPoint(x: 0.5, y: 1.0) - Neeraj Joshi

17

这很容易

    // MARK: - Gradient
extension CAGradientLayer {
    enum Point {
        case topLeft
        case centerLeft
        case bottomLeft
        case topCenter
        case center
        case bottomCenter
        case topRight
        case centerRight
        case bottomRight
        var point: CGPoint {
            switch self {
            case .topLeft:
                return CGPoint(x: 0, y: 0)
            case .centerLeft:
                return CGPoint(x: 0, y: 0.5)
            case .bottomLeft:
                return CGPoint(x: 0, y: 1.0)
            case .topCenter:
                return CGPoint(x: 0.5, y: 0)
            case .center:
                return CGPoint(x: 0.5, y: 0.5)
            case .bottomCenter:
                return CGPoint(x: 0.5, y: 1.0)
            case .topRight:
                return CGPoint(x: 1.0, y: 0.0)
            case .centerRight:
                return CGPoint(x: 1.0, y: 0.5)
            case .bottomRight:
                return CGPoint(x: 1.0, y: 1.0)
            }
        }
    }
    convenience init(start: Point, end: Point, colors: [CGColor], type: CAGradientLayerType) {
        self.init()
        self.startPoint = start.point
        self.endPoint = end.point
        self.colors = colors
        self.locations = (0..<colors.count).map(NSNumber.init)
        self.type = type
    }
}

使用方法如下:

let fistColor = UIColor.white
let lastColor = UIColor.black
let gradient = CAGradientLayer(start: .topLeft, end: .topRight, colors: [fistColor.cgColor, lastColor.cgColor], type: .radial)
gradient.frame = yourView.bounds
yourView.layer.addSublayer(gradient)

为枚举类型 Point 投票 - Tà Truhoada
制作(类型:.axial) - Milan Savaliya M

11

使用这个自定义类扩展UIView


GradientView.swift

import UIKit

class GradientView: UIView {

    // Default Colors
    var colors:[UIColor] = [UIColor.redColor(), UIColor.blueColor()]

    override func drawRect(rect: CGRect) {

        // Must be set when the rect is drawn
        setGradient(colors[0], color2: colors[1])
    }

    func setGradient(color1: UIColor, color2: UIColor) {

        let context = UIGraphicsGetCurrentContext()
        let gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [color1.CGColor, color2.CGColor], [0, 1])!

        // Draw Path
        let path = UIBezierPath(rect: CGRectMake(0, 0, frame.width, frame.height))
        CGContextSaveGState(context)
        path.addClip()
        CGContextDrawLinearGradient(context, gradient, CGPointMake(frame.width / 2, 0), CGPointMake(frame.width / 2, frame.height), CGGradientDrawingOptions())
        CGContextRestoreGState(context)
    }

    override func layoutSubviews() {

        // Ensure view has a transparent background color (not required)
        backgroundColor = UIColor.clearColor()
    }

}

使用方法

gradientView.colors = [UIColor.blackColor().colorWithAlphaComponent(0.8), UIColor.clearColor()]


结果

这里输入图片描述


你没有调用super.drawRect()的特定原因吗? - Rafał Sroka
@Bearwithme 不需要。只需添加 super.drawRect() 就可以正常工作。 - Michael
1
最好在GradientView.swift的顶部添加以下内容:@IBInspectable var topColor: UIColor = UIColor.lightGrayColor()@IBInspectable var bottomColor: UIColor = UIColor.blueColor(),然后您可以在设置中看到属性编辑器 - Dmitry Senashenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接