如何将 CAGradientLayer 应用于 UINavigationController 背景
How can I apply CAGradientLayer to a UINavigationController background
我有一个 UINavigationController
和 prefersLargeTitles = 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 { [=10=].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()
}
}
为了回答您的问题,我认为您的 BrandedHeader
init 中的 gradient.frame = frame
没有做任何事情。我相信您明确需要在 layoutSubviews
.
中设置 height
道具
类似于 gradient.frame = CGRect(x: 0, y: 0, width: frame.width, height: 500)
- 尽管我还没有尝试过,所以请考虑调整值
我不确定这种方法是否是您想要实现的最佳方法,一旦我尝试了另一种方法来实现这一点,我会回来更新这个答案。
以下代码展示了如何制作一个带有渐变色的导航栏。在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。
我有一个 UINavigationController
和 prefersLargeTitles = 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 { [=10=].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()
}
}
为了回答您的问题,我认为您的 BrandedHeader
init 中的 gradient.frame = frame
没有做任何事情。我相信您明确需要在 layoutSubviews
.
height
道具
类似于 gradient.frame = CGRect(x: 0, y: 0, width: frame.width, height: 500)
- 尽管我还没有尝试过,所以请考虑调整值
我不确定这种方法是否是您想要实现的最佳方法,一旦我尝试了另一种方法来实现这一点,我会回来更新这个答案。
以下代码展示了如何制作一个带有渐变色的导航栏。在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。