如何将 CAGradientLayer 应用于 UINavigationController 背景

Posted

技术标签:

【中文标题】如何将 CAGradientLayer 应用于 UINavigationController 背景【英文标题】:How can I apply CAGradientLayer to a UINavigationController background 【发布时间】:2019-03-30 07:49:21 【问题描述】:

我有一个 UINavigationControllerprefersLargeTitles = true 我想为我的导航栏应用背景颜色,并带有渐变蒙版,因此颜色基本上会逐渐变暗。

我试图创建一个UIView 应用蒙版并渲染它。然而,这涉及将UIView 渲染为UIImage,然后渲染为UIColor,这样我就可以设置barTintColor

这在仅渲染 UIView 时有效,但在导航栏上设置它时,行为不是我想要的。

我希望渐变填充整个绿色区域。

class BaseViewController: UIViewController 

    override func viewDidLoad() 
        super.viewDidLoad()

        configureNavigationBar()
    

    override var preferredStatusBarStyle: UIStatusBarStyle 
        return .lightContent
    

    func hideNavigationBar() 
        navigationController?.setNavigationBarHidden(true, animated: false)
    

    func showNavigationBar() 
        navigationController?.setNavigationBarHidden(false, animated: false)
    

    private func configureNavigationBar() 
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationBar.isTranslucent = false
        navigationItem.title = "Home"

        let v = BrandedHeader(frame: .zero)
        v.frame = navigationController?.navigationBar.frame ?? .zero

        navigationController?.navigationBar.barTintColor = UIColor(patternImage: v.asImage()!)
    


extension UIImage 
    static func usingHex(_ hex: String) -> UIImage 
        let color = UIColor.usingHex(hex)
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor)
        context!.fill(rect)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    


extension UIView 

    func asImage() -> UIImage? 
        if #available(ios 10.0, *) 
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image  rendererContext in
                layer.render(in: rendererContext.cgContext)
            
         else 
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
            defer  UIGraphicsEndImageContext() 
            guard let currentContext = UIGraphicsGetCurrentContext() else 
                return nil
            
            self.layer.render(in: currentContext)
            return UIGraphicsGetImageFromCurrentImageContext()
        
    


class BrandedHeader: UIView 
    let gradient: CAGradientLayer = CAGradientLayer()

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

        backgroundColor = UIColor.green

        gradient.frame = frame
        gradient.colors = [UIColor.clear, UIColor.black.withAlphaComponent(0.3)].map  $0.cgColor 
        gradient.locations = [0.0, 0.7]
        gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
        gradient.endPoint = CGPoint(x: 1.0, y: 0.0)
        layer.insertSublayer(gradient, at: 0)
    

    required init?(coder aDecoder: NSCoder) 
        return nil
    

    override func layoutSubviews() 
        super.layoutSubviews()
        gradient.frame = frame
        gradient.removeAllAnimations()
    

【问题讨论】:

【参考方案1】:

要回答您的问题,我认为您的 BrandedHeader init 中的 gradient.frame = frame 没有做任何事情。我相信您明确需要在layoutSubviews 中设置height 属性。

gradient.frame = CGRect(x: 0, y: 0, width: frame.width, height: 500) 之类的东西 - 虽然我没有尝试过,所以请考虑调整值

我不确定这种方法是否是您想要实现的最佳方法,一旦我尝试了另一种方法来实现这一目标,我会回来并更新这个答案。

【讨论】:

【参考方案2】:

下面的代码展示了如何用渐变色制作导航栏。它在 Swift 5 下测试。

// ViewController.swift //
import UIKit

class ViewController: UIViewController 
    // MARK: - Variables

    // MARK: - IBOutlet

    // MARK: - IBAction

    // MARK: - Life cycle
    override func viewDidLoad() 
        super.viewDidLoad()

        let titleString = "Home"
        let navBar = navigationController!.navigationBar
        let atext = NSMutableAttributedString(string: titleString)
        let range = NSMakeRange(0, titleString.count)
        atext.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], range: range)
        atext.addAttributes([NSAttributedString.Key.strokeColor: UIColor.yellow], range: range)
        atext.addAttributes([NSAttributedString.Key.strokeWidth: NSNumber.init(value: -1.0)], range: range)
        let titleLabel: UILabel = UILabel.init(frame: CGRect(x: 50, y: 3, width: 220-100, height: 44))
        titleLabel.attributedText = atext
        titleLabel.textAlignment = NSTextAlignment.center
        titleLabel.font = UIFont(name: "Helvetica", size: 24.0)
        self.navigationItem.titleView = titleLabel

        let rect = CGRect(x: 0, y: -20, width: (self.navigationController?.navigationBar.bounds.width)!, height: (self.navigationController?.navigationBar.bounds.height)! + 20)
        let colors = [UIColor.blue, UIColor.purple, UIColor.red]
        let points: [CGFloat] = [0.1, 0.5, 0.9]
        let gradientView = GradientView(frame: rect, backColor: UIColor.white, gradientColors: colors, points: points, isVertical: true)
        gradientView.backgroundColor = UIColor.white
        navBar.insertSubview(gradientView, at: 1)

        navBar.layer.shadowColor = UIColor.black.cgColor
        navBar.layer.shadowRadius = 4.0
        navBar.layer.shadowOpacity = 0.6
        navBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
    


// GradientView.swift (subclass of UIView) //
import UIKit

class GradientView: UIView 
    var backColor: UIColor
    var gradientColors: [UIColor]
    var points: [CGFloat]
    var isVertical: Bool

    init(frame: CGRect, backColor: UIColor, gradientColors: [UIColor], points: [CGFloat], isVertical: Bool) 
        self.backColor = backColor
        self.gradientColors = gradientColors
        self.points = points
        self.isVertical = isVertical
        super.init(frame: frame)
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func draw(_ rect: CGRect) 
        super.draw(rect)
        backColor.set()
        UIRectFill(rect)

        let gradient: CAGradientLayer = CAGradientLayer()
        gradient.frame = self.bounds

        var colors = [CGColor]()
        for i in 0..<gradientColors.count 
            let color = gradientColors[i]
            colors.append(color.cgColor)
        
        gradient.colors = colors

        var locations = [CGFloat]()
        for i in 0..<points.count 
            let point = points[i]
            locations.append(point)
        
        gradient.locations = locations as [NSNumber]
        if !isVertical 
            gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
            gradient.endPoint = CGPoint(x: 1.0, y: 0.0)
        
        layer.addSublayer(gradient)
    

如果您想水平运行颜色(从左到右),请将isVertical 开关设置为 false。

【讨论】:

以上是关于如何将 CAGradientLayer 应用于 UINavigationController 背景的主要内容,如果未能解决你的问题,请参考以下文章

使用编程约束时如何将 CAGradientLayer 添加到 UIView

UICollectionViewCell - 将不同的 CAGradientLayer 应用于不同的 UIView

CAGradientLayer 未应用于整个屏幕

在动态大小的 UITableViewCell 上应用 CAGradientLayer

如何使用 Swift 3 或 Hue 将 CAGradientLayer 转换为 UIColor?

如何将 CAGradientLayer 添加到 UIButton