创建具有不同起点和终点的多个渐变视图的最佳方法是什么



在我的项目中,我有多个具有不同颜色和不同起点和终点的渐变视图。某些渐变视图还具有带有拐角半径的阴影。

出于这个原因,我为每个渐变要求创建了多个视图类型。每个GradiantView在多个地方使用。

前任:

struct ApplyGradiantView {
var GradiantLayer : CAGradientLayer?
init(frame:CGRect,Colors:[CGColor],startPoint:CGPoint,endPoint:CGPoint) {
GradiantLayer = CAGradientLayer()
GradiantLayer?.colors = Colors
GradiantLayer?.startPoint = startPoint
GradiantLayer?.endPoint = endPoint
GradiantLayer?.frame = frame
}
}

class blueGradiantView : UIView {
var gradiant1Colour = AppColor.gradiantColor1.cgColor
var gradiant2Colour = AppColor.gradiantColor2.cgColor
var renderOnese = false
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
if !renderOnese {
ApplyCustomeView()
renderOnese = true
}
}
func ApplyCustomeView() {
let GradiantLayer = ApplyGradiantView(frame: self.bounds, Colors: [gradiant1Colour,gradiant2Colour], startPoint: CGPoint(x: 0.0, y: 0.0), endPoint: CGPoint(x: 0.8, y: 1.0))
if let gradiantLayer = GradiantLayer.GradiantLayer {
self.layer.insertSublayer(gradiantLayer, at: 0)
}
}
}

class RedGradiantView : UIView {
var inerGradiantView = UIView()
var gradiant1Colour = AppColor.gradiantColor3.cgColor
var gradiant2Colour = AppColor.gradiantColor4.cgColor
var renderOnese = false
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
if !renderOnese {
ApplyCustomeView()
//            renderOnese = true
}
}
private func ApplyCustomeView() {
inerGradiantView.frame = self.bounds.insetBy(dx: 0, dy: 0)
self.insertSubview(inerGradiantView, at: 0)
let GradiantLayer = ApplyGradiantView(frame: self.bounds, Colors: [gradiant1Colour,gradiant2Colour], startPoint: CGPoint(x: 0.0, y: 0.0), endPoint: CGPoint(x: 0.8, y: 1.0))
if let gradiantLayer = GradiantLayer.GradiantLayer {
inerGradiantView.layer.insertSublayer(gradiantLayer, at: 0)
}
inerGradiantView.layer.cornerRadius = 5
inerGradiantView.clipsToBounds = true
self.layer.cornerRadius = 5
self.clipsToBounds = true
self.backgroundColor = UIColor.white.withAlphaComponent(0.2)
}
}

class ShadowWithRedBluer : UIView {
var inerGradiantView = UIView()
var gradiant1Colour = AppColor.gradiantColor3.cgColor
var gradiant2Colour = AppColor.gradiantColor4.cgColor
var renderOnese = false
private var shadowLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
if !renderOnese {
ApplyCustomeView()
//            renderOnese = true
}
}
private func ApplyCustomeView() {
inerGradiantView.frame = self.bounds.insetBy(dx: 2, dy: 2)
self.insertSubview(inerGradiantView, at: 0)
let GradiantLayer = ApplyGradiantView(frame: self.bounds, Colors: [gradiant1Colour,gradiant2Colour], startPoint: CGPoint(x: 0.0, y: 0.0), endPoint: CGPoint(x: 0.8, y: 1.0))
if let gradiantLayer = GradiantLayer.GradiantLayer {
inerGradiantView.layer.insertSublayer(gradiantLayer, at: 0)
}
inerGradiantView.layer.cornerRadius = 20
inerGradiantView.clipsToBounds = true
self.layer.applySketchShadow(color: UIColor.black, alpha: 0.15, x: 0, y: 50, blur: 50, spread: 0)
self.backgroundColor = UIColor.clear
}        
}
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}

class ShadowOnlyView : UIView {
var renderOnese = false
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
if !renderOnese {
ApplyCustomeView()
//            renderOnese = true
}
}
private func ApplyCustomeView() {
self.layer.applySketchShadow(color: UIColor.black, alpha: 0.15, x: 0, y: 50, blur: 50, spread: 0)
self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
self.layer.cornerRadius = 10
self.clipsToBounds = true
}
}

如何使用面向协议的编程和泛型来减少重复代码?

你问:

如何使用面向协议的编程和泛型来减少重复代码?

虽然我将在下面向您展示示例,TL;DR的答案是,你应该坚持UIKit的面向对象模式,而不是面向协议的编程和泛型。你想要连接到UIKit机制,POP和泛型将引入更多的问题,而不是它们的价值。

所以,我建议UIViewGradientView子类来完成所有梯度工作。如果你想用一组标准的颜色重用它(例如,重复使用一个RedGradientView,重复重用一个BlueGradientView,那么从GradientView定义子类,这不会重新实现渐变的东西,而只是相应地更新颜色)。

所以我可以这样定义一个GradientView

@IBDesignable
class GradientView: UIView {
// Inspectables
@IBInspectable var startColor: UIColor = .white                { didSet { updateGradient() } }
@IBInspectable var endColor:   UIColor = .blue                 { didSet { updateGradient() } }
@IBInspectable var startPoint: CGPoint = CGPoint(x: 0.5, y: 0) { didSet { updateGradient() } }
@IBInspectable var endPoint:   CGPoint = CGPoint(x: 0.5, y: 1) { didSet { updateGradient() } }
// UIView gradient layers
override static var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
// initialization methods
override init(frame: CGRect = .zero) {
super.init(frame: frame)
updateGradient()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
updateGradient()
}
}
private extension GradientView {
func updateGradient() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
}
}

请注意以下几点:

  • 不是添加渐变层作为子图层并在layoutSubviews中更新,我只需将视图的layerClass定义为CAGradientLayer,然后你就不需要担心layoutSubviews了。这似乎是一个次要的观察结果,但如果您曾经使用上面的layerClass方法对视图大小的调整进行动画处理,则渐变会在动画中间正确呈现。如果使用layoutSubviews方法,则会对渐变进行不和谐的大小调整。

  • 我已经做了这个@IBDesignable.您不需要这样做,但如果您想在 IB 中看到正确呈现,这将非常有用。

  • 请注意,我避免实现自定义init方法。您确实希望保留在已建立的UIView初始值设定项中(以便您可以在故事板中使用这些初始值设定项,而不必使用非标准初始化等乱扔代码)。

  • 一个非常小的观察,但我建议始终以小写字母开头您的属性和变量(例如,使用gradientLayer而不是GradientLayer)。同样,我建议始终以大写字母开头的类名(例如,我称之为BlueGradiantView而不是blueGradiantView)。

  • 虽然我将向您展示下面的各种红色和蓝色类,但我建议不要在类名中合并颜色的实际名称。如果您更改颜色主题,您真的不想更改类名。哈哈。

    我还建议,如果针对iOS 11及更高版本,则放弃使用AppColor类型,而是使用命名颜色(您可以通过编程方式以及直接在IB中访问)。

无论如何,如果您真的想要一个BlueGradientView类型,它可能看起来像:

@IBDesignable
class BlueGradientView: GradientView {
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
private extension BlueGradientView {
func configure() {
startColor = AppColor.gradientColor1
endColor = AppColor.gradientColor2
}
}

同样RedGradientView

@IBDesignable
class RedGradientView: GradientView {
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
private extension RedGradientView {
func configure() {
startColor = AppColor.gradientColor3
endColor = AppColor.gradientColor4
}
}

还有RoundedRedWithShadowGradientView

@IBDesignable
class RoundedRedWithShadowGradientView: RedGradientView {
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
private extension RoundedRedWithShadowGradientView {
func configure() {
applyShadow()
layer.cornerRadius = 10
}
}

您可能有这样的扩展名:

extension UIView {
func applyShadow(color: UIColor = .black,
alpha: Float = 0.5,
shadowOffset: CGSize = CGSize(width: 0, height: 2),
blur: CGFloat = 4)
{
layer.shadowColor = color.cgColor
layer.shadowOpacity = alpha
layer.shadowOffset = shadowOffset
layer.shadowRadius = blur / 2.0
}
}

请注意,如果可以的话,我会避免使用shadowPath(它简化了动画,避免了需要实现layoutSubviews)。但随心所欲。但是要确保shadowPath方法提供了一些重要的实用性,而这些实用性不能仅仅通过拐角半径来实现,因为您不希望有一个不必要的脆弱解决方案。

现在,我怀疑你会读到所有这些并说"但我想使用面向协议的模式和泛型",但是虽然这些都是很棒的 Swift 功能,但你不应该与 UIKit 固有的 OOP 设计作斗争。在幕后,UIKit 仍然是 Objective-C,而仅 Swift 模式将无法提供您真正想要的互操作性。也许当你转向 SwiftUI 时,你可以重新审视这一点,但只要你使用 UIKit,就坚持使用 UIKit 的原生 OOP 模式。

方法-1

您可以创建UIViewextension并为其创建addGradient(colors:start:end:),而不是创建用于添加gradientstruct,即

extension UIView {
func addGradient(colors: [CGColor], start: CGPoint, end: CGPoint) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors
gradientLayer.startPoint = start
gradientLayer.endPoint = end
gradientLayer.frame = self.bounds
self.layer.addSublayer(gradientLayer)
}
}

用法:

self.view.addGradient(colors: [UIColor.blue.cgColor, UIColor.white.cgColor], start: .zero, end: CGPoint(x: 0, y: 1))

方法-2

如果您通过storyboard执行此操作,您可以使用@IBDesignable@IBInspectable通过storyboard本身进行更改,即

@IBDesignable
class DesignableView: UIView {
@IBInspectable var gradientColor1: UIColor = UIColor.white {
didSet{
self.setGradient()
}
}
@IBInspectable var gradientColor2: UIColor = UIColor.white {
didSet{
self.setGradient()
}
}
@IBInspectable var gradientStartPoint: CGPoint = .zero {
didSet{
self.setGradient()
}
}
@IBInspectable var gradientEndPoint: CGPoint = CGPoint(x: 0, y: 1) {
didSet{
self.setGradient()
}
}
private func setGradient()
{
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [self.gradientColor1.cgColor, self.gradientColor2.cgColor]
gradientLayer.startPoint = self.gradientStartPoint
gradientLayer.endPoint = self.gradientEndPoint
gradientLayer.frame = self.bounds
if let topLayer = self.layer.sublayers?.first, topLayer is CAGradientLayer
{
topLayer.removeFromSuperlayer()
}
self.layer.addSublayer(gradientLayer)
}
}

只需将storyboardUIViewclass设置为DesignableView,并设置gradientColor1gradientColor2gradientStartPointgradientEndPoint的属性。

编辑-1:

您可以根据需要创建一个enum Gradient并为gradients进行多次cases,即

enum Gradient {
case blue(CGRect)
case red(CGRect)
var gradient: CAGradientLayer {
switch self {
case .blue(let frame):
return self.gradient(colors: [.blue,.white], start: .zero, end: CGPoint(x: 0, y: 1), frame: frame)
case .red(let frame):
return self.gradient(colors: [.red,.white], start: .zero, end: CGPoint(x: 1, y: 0), frame: frame)
}
}
func gradient(colors: [UIColor], start: CGPoint, end: CGPoint, frame: CGRect) -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors.map({$0.cgColor})
gradientLayer.startPoint = start
gradientLayer.endPoint = end
gradientLayer.frame = frame
return gradientLayer
}
}

用法:

let gradient = Gradient.blue(view.bounds).gradient
self.view.layer.addSublayer(gradient)

最新更新