径向CAGradientLayer在运行时无法正确呈现

时间:2019-09-11 12:40:20

标签: ios swift cagradientlayer radial-gradients

我正在尝试快速创建一个径向CAGradientLayer。普通的CAGradientLayers(默认类型为axial)没有问题。它们可以在快照和模拟器的运行时上很好地呈现。

当我为创建的UIView拍摄快照(仅用于CAGradientLayer)时,它显示了一个漂亮的径向CAGradientLayer。但是,当我使用此视图并在模拟器中的运行时对其进行查看时,它看起来很糟糕。我不知道我在做什么错。

这是CAGradientLayer的代码:

import Foundation

class ChannelGradientView: UIView {

  // MARK: - Init

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

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

  // MARK: - Setup

  private func setup() {
    backgroundColor = .clear
    setupGradient()
  }

  private func setupGradient() {
    let gradient = layer as? CAGradientLayer
    gradient?.type = .radial

    gradient?.colors = [
      UIColor.black.withAlphaComponent(0.8),
      UIColor.black.withAlphaComponent(0)
    ].map { $0.cgColor }

    let blackPoint = CGPoint(x: 1, y: 0)
    let clearPoint = CGPoint(x: 1, y: 1)

    // startpoint is center (for example black here)
    gradient?.startPoint = blackPoint
    gradient?.endPoint = clearPoint
  }

  // MARK: - Layer

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

这是我进行快照单元测试(使用Nimble Snapshot库)时的样子:

enter image description here

这是在模拟器上运行时的样子:

enter image description here enter image description here

有人知道我在做什么错吗?

编辑:在实际设备上运行,它甚至不显示任何渐变层,甚至不显示任何渐变层。这是一个透明的盒子。

2 个答案:

答案 0 :(得分:1)

不确定在模拟器与设备上看到的内容之间可能还会发生什么,但是...

如果我照原样使用您的代码,则会收到此消息(红色边框显示框架):

enter image description here

如果我更改了您的clearPoint

//let clearPoint = CGPoint(x: 1, y: 1)
let clearPoint = CGPoint(x: 0, y: 1)    // bottom-left-corner

我明白了:

enter image description here

模拟器和设备上的外观相同


编辑

一些其他说明...

径向渐变不像.startPoint(线性)渐变那样使用.endPoint.axial

使用.radial,以.startPoint为中心绘制渐变 *椭圆,并进行startPoint.xendPoint.x次之间的差2为宽度,startPoint.yendPoint.y之间的差乘以2为高度。

因此,要获得所需的从右上到左下的径向渐变,需要将.startPoint设置为1,0,并将.endPoint设置为导致椭圆形渐变的值大小2 x 2

startPoint = 1,0

endPoint = 0,1

    width:  abs(1 - 0) * 2 = 2
    height: abs(0 - 1) * 2 = 2

请注意,您可以通过以下方式获得相同的结果:

startPoint = 1,0

endPoint = 2,-1

    width:  abs(1 - 2) * 2 = 2
    height: abs(0 - (-1)) * 2 = 2

.radial渐变 不是 的样子是将其宽度或高度设置为零,这就是我们得到的:

startPoint = 1,0

endPoint = 1,1

    width:  abs(1 - 1) * 2 = 0   // problem!!!!
    height: abs(0 - 1) * 2 = 2

我们已经看到,结果是奇怪的线条图案。

这里有一些实际的例子来演示。

轴向/线性从右上到左下:

enter image description here

径向居中且宽度=视图宽度/高度=视图高度:

enter image description here

径向居中且宽度=一半视图宽度/高度=视图高度:

enter image description here

径向居中且宽度=一半视图宽度/高度=视图高度...完全相同的结果,但请注意.endPoint.x的值为0.75而不是0.25:< / p>

enter image description here

现在,我们更改.startPoint.x = 0.75,但是我们离开.endPoint.x = 0.25,因此椭圆的中心移至视图宽度的3/4,但椭圆的宽度变得等于宽度视图的位置... abs(0.75 - 0.25) * 2 == 1.0

enter image description here

更改.endPoint.x = 0.5,宽度返回到视图宽度的1/2 ... abs(0.75 - 0.5) * 2 = 0.5

enter image description here

最后是从右上到左下的径向渐变:

enter image description here

这是我用来生成这些图像的代码。它具有“梯度定义”的数据块。您可以添加/更改这些定义以尝试差异。

//
//  GradTestViewController.swift
//
//  Created by Don Mag on 9/12/19.
//

import UIKit

class TestGradientView: UIView {

    // MARK: - Init
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

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

    // MARK: - Setup
    private func setup() {

        // just set the background to clear and the
        //  gradient colors to blue -> green
        backgroundColor = .clear

        if let gradient = layer as? CAGradientLayer {
            gradient.colors = [
                UIColor.blue,
                UIColor.green,
                ].map { $0.cgColor }
        }

    }

    // MARK: - Layer
    override public class var layerClass: Swift.AnyClass {
        return CAGradientLayer.self
    }

}

struct GradDef {
    var gradType: CAGradientLayerType = .axial
    var startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0)
    var endPoint: CGPoint = CGPoint(x: 0.0, y: 1.0)
}

class GradTestViewController: UIViewController {

    var theButton: UIButton = {
        let v = UIButton()
        v.setTitle("Tap", for: .normal)
        v.setTitleColor(.white, for: .normal)
        v.setTitleColor(.lightGray, for: .highlighted)
        v.backgroundColor = .red
        return v
    }()

    var counterLabel: UILabel = {
        let v = UILabel()
        return v
    }()

    var descLabel: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        return v
    }()

    var gradContainerView: UIView = {
        let v = UIView()
        return v
    }()

    var gradView: TestGradientView = {
        let v = TestGradientView()
        return v
    }()

    var tlLabel: UILabel = {
        let v = UILabel()
        v.text = "0,0"
        return v
    }()

    var trLabel: UILabel = {
        let v = UILabel()
        v.text = "1,0"
        return v
    }()

    var blLabel: UILabel = {
        let v = UILabel()
        v.text = "0,1"
        return v
    }()

    var brLabel: UILabel = {
        let v = UILabel()
        v.text = "1,1"
        return v
    }()

    var theStackView: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        v.alignment = .center
        v.distribution = .fill
        v.spacing = 20.0
        return v
    }()

    var gradDefs: [GradDef] = [
        GradDef(gradType: .axial,  startPoint: CGPoint(x: 1.0,  y: 0.0), endPoint: CGPoint(x: 0.0,  y: 1.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 0.5,  y: 0.5), endPoint: CGPoint(x: 0.0,  y: 0.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 0.5,  y: 0.5), endPoint: CGPoint(x: 0.25, y: 0.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 0.5,  y: 0.5), endPoint: CGPoint(x: 0.75, y: 0.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 0.75, y: 0.5), endPoint: CGPoint(x: 0.25, y: 0.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 0.75, y: 0.5), endPoint: CGPoint(x: 1.0,  y: 0.0)),
        GradDef(gradType: .radial, startPoint: CGPoint(x: 1.0,  y: 0.0), endPoint: CGPoint(x: 0.0,  y: 1.0)),
    ]

    var idx: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        [theButton, counterLabel, gradContainerView, gradView, tlLabel, trLabel, blLabel, brLabel, descLabel, theStackView].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        [theButton, counterLabel, gradContainerView, descLabel].forEach {
            theStackView.addArrangedSubview($0)
        }

        [gradView, tlLabel, trLabel, blLabel, brLabel].forEach {
            gradContainerView.addSubview($0)
        }

        [counterLabel, tlLabel, trLabel, blLabel, brLabel, descLabel].forEach {
            $0.font = UIFont.monospacedDigitSystemFont(ofSize: 14.0, weight: .regular)
        }

        NSLayoutConstraint.activate([

            gradView.widthAnchor.constraint(equalToConstant: 120),
            gradView.heightAnchor.constraint(equalTo: gradView.widthAnchor),
            gradView.centerXAnchor.constraint(equalTo: gradContainerView.centerXAnchor),
            gradView.centerYAnchor.constraint(equalTo: gradContainerView.centerYAnchor),

            tlLabel.centerXAnchor.constraint(equalTo: gradView.leadingAnchor, constant: 0.0),
            blLabel.centerXAnchor.constraint(equalTo: gradView.leadingAnchor, constant: 0.0),
            trLabel.centerXAnchor.constraint(equalTo: gradView.trailingAnchor, constant: 0.0),
            brLabel.centerXAnchor.constraint(equalTo: gradView.trailingAnchor, constant: 0.0),

            tlLabel.bottomAnchor.constraint(equalTo: gradView.topAnchor, constant: -2.0),
            trLabel.bottomAnchor.constraint(equalTo: gradView.topAnchor, constant: -2.0),

            blLabel.topAnchor.constraint(equalTo: gradView.bottomAnchor, constant: 2.0),
            brLabel.topAnchor.constraint(equalTo: gradView.bottomAnchor, constant: 2.0),

            tlLabel.topAnchor.constraint(equalTo: gradContainerView.topAnchor, constant: 4.0),
            tlLabel.leadingAnchor.constraint(equalTo: gradContainerView.leadingAnchor, constant: 4.0),

            brLabel.trailingAnchor.constraint(equalTo: gradContainerView.trailingAnchor, constant: -4.0),
            brLabel.bottomAnchor.constraint(equalTo: gradContainerView.bottomAnchor, constant: -4.0),

            ])

        view.addSubview(theStackView)

        NSLayoutConstraint.activate([

            theStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
            theStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            theButton.widthAnchor.constraint(equalToConstant: 160.0),
            descLabel.widthAnchor.constraint(equalToConstant: 240.0),

            ])

        theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)

        idx = -1
        didTap(nil)
    }

    @IBAction func didTap(_ sender: Any?) {

        guard let gLayer = gradView.layer as? CAGradientLayer else {
            fatalError("Could not get the gradient layer!")
        }

        idx += 1

        if idx >= gradDefs.count {
            idx = 0
        }

        let gDef = gradDefs[idx]

        gLayer.type = gDef.gradType
        gLayer.startPoint = gDef.startPoint
        gLayer.endPoint = gDef.endPoint

        var s = ""
        s += "Gradient Type: " + (gDef.gradType == CAGradientLayerType.axial ? "Axial" : "Radial")
        s += "\n\n"
        s += "Start Point: \(gDef.startPoint)"
        s += "\n"
        s += "End Point:   \(gDef.endPoint)"

        if gDef.gradType == CAGradientLayerType.radial {
            let w = abs(gDef.startPoint.x - gDef.endPoint.x) * 2
            let h = abs(gDef.startPoint.y - gDef.endPoint.y) * 2
            s += "\n\n"
            s += "\t" + "Radial Width:"
            s += "\n"
            s += "\t\t" + "abs(\(gDef.startPoint.x) - \(gDef.endPoint.x)) * 2 == \(w)"
            s += "\n\n"
            s += "\t" + "Radial Height:"
            s += "\n"
            s += "\t\t" + "abs(\(gDef.startPoint.y) - \(gDef.endPoint.y)) * 2 == \(h)"
        }

        s += "\n"

        descLabel.text = s

        counterLabel.text = "Variation \(idx + 1) of \(gradDefs.count)"

    }

}

一切都用代码完成-没有@IBOutlets@IBActions,因此只需创建一个新的视图控制器并将其自定义类分配给GradTestViewController

答案 1 :(得分:0)

这是解决方法,请按照以下步骤设置要点:

    let blackPoint = CGPoint(x: 1.0, y: 0.0)
    let clearPoint = CGPoint(x: 0.0, y: 1.0)

如果您的环境还可以,请告诉我。

-

我将您的整个代码放在操场上,如下所示,进行了一些调整(将渐变贴在右上角):

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport



class ChannelGradientView: UIView {

  // MARK: - Init

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

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

  // MARK: - Setup

  private func setup() {
    backgroundColor = .clear
    setupGradient()
  }

  private func setupGradient() {
    guard let gradient = layer as? CAGradientLayer else { return }
    //let gradient = CAGradientLayer()
    gradient.type = .radial
    gradient.frame = frame
    gradient.colors = [
        UIColor.black.cgColor,
        UIColor.clear.cgColor
    ]

    let blackPoint = CGPoint(x: 1.0, y: 0.0)
    let clearPoint = CGPoint(x: 0.0, y: 1.0)

    // startpoint is center (for example black here)
    gradient.startPoint = blackPoint
    gradient.endPoint = clearPoint
  }

  // MARK: - Layer

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


class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        self.view = view

        view.backgroundColor = .white

        let frame = CGRect(x: 150, y: 200, width: 200, height: 200)

        let iv = UIImageView()
        iv.frame = frame
        iv.alpha = 0.8
        iv.contentMode = .scaleAspectFill
        if let url = URL(string: "https://images-na.ssl-images-amazon.com/images/I/61VpNkHPRoL._SX679_.jpg"),
            let img = try? Data(contentsOf: url) {
            iv.image = UIImage(data: img)
        }
        view.addSubview(iv)
        iv.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            iv.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        iv.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        iv.topAnchor.constraint(equalTo: view.topAnchor),
        iv.bottomAnchor.constraint(equalTo: view.bottomAnchor),

        ])


        let gd = ChannelGradientView()
        view.addSubview(gd)
        gd.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
        gd.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        gd.topAnchor.constraint(equalTo: view.topAnchor),
        gd.widthAnchor.constraint(equalTo: view.widthAnchor),
        gd.heightAnchor.constraint(equalTo: gd.widthAnchor),

        ])
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

它确实显示正确:

enter image description here