【问题标题】:What is best way to create multiple gradient views with different start and end points创建具有不同起点和终点的多个渐变视图的最佳方法是什么
【发布时间】:2019-06-22 01:30:02
【问题描述】:

在我的项目中,我有多个渐变视图,它们具有不同的颜色以及不同的起点和终点。一些渐变视图也有带角半径的阴影。

出于这个原因,我为每个渐变要求创建了多个视图类型。每个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
    }

}

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

【问题讨论】:

    标签: ios swift


    【解决方案1】:

    你问:

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

    虽然我将在下面向您展示示例,但 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-only 模式不会提供你真正想要的互操作性。也许当您转向 SwiftUI 时,您可以重新审视这一点,但只要您使用 UIKit,请坚持使用 UIKit 的原生 OOP 模式。

    【讨论】:

      【解决方案2】:

      方法一

      您可以创建一个UIViewextension 并为其创建一个addGradient(colors:start:end:),而不是创建一个struct 来添加gradient,即

      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的属性。

      Edit-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)
      

      【讨论】:

      • 嗨@PGDev不错的解决方案。但是我在多个地方使用那些渐变视图。稍后如果我想更改某些渐变的颜色,我只需要在一个地方更改(特定的渐变视图)。您的解决方案我必须在多个地方进行更改。
      猜你喜欢
      • 2014-11-28
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2021-08-05
      • 2020-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多