美文网首页界面iOS学习笔记iOS Developer
iOS Review | 你所不知道的10种Layer

iOS Review | 你所不知道的10种Layer

作者: 清無 | 来源:发表于2017-09-15 18:22 被阅读110次

    1. CALayer

    UIView和CALayer的关系
    UIView和CALayer的关系
    • 一个View只能有一个Root Layer;
    • 一个Layer可以包含多个Sub Layer;
    • View只负责子元素的布局Layout和事件处理Events
    • Layer负责View上内容的绘制drawRect和显示,以及动画CAAnimation
    CALayer Basic Properties
    • corner 圆角
    let layer = view.layer
    layer.masksToBounds = true // 必须为true
    layer.cornerRadius = 10
    
    • border 边框
    let layer = view.layer
    layer.borderColor = UIColor.red.cgColor
    layer.borderWidth = 10
    
    • shadow 投影
    let layer = view.layer
    layer.shadowColor = UIColor.green.cgColor
    layer.shadowRadius = 10 // 半径
    layer.shadowOpacity = 0.5 // 透明度
    layer.shadowOffset = CGSize(width: 0, height: 10) // 偏移: x, y
    
    • contents 只能设置为图片CGImage / NSImage
    let layer = view.layer
    layer.contentsGravity = kCAGravityCenter
    layer.contentsScale = UIScreen.main.scale
    layer.contents = UIImage.init(named: "star")?.cgImage
    
    • shouldRasterize 栅格化

      • 默认是false,设置为true时,layer只被渲染一次(相当于一张静态图片);
      • 这适用于做一些与appearance无关的动画(如posistion, scale, rotate)等;
      • 可以很大程度提升性能(如果layer上内容比较复杂的话);
    • drawsAsynchronously 异步绘制

      • 和shouldRasterize的功能相反,为异步连续多次绘制;
      • 默认是false,为true时,如果layer必须得连续重新绘制时,可以提升性能(例如粒子发射layer);

    2. CAScrollLayer

    • CAScrollLayer其实是一个比较简单功能的类,它只有一个有用的方法scroll(to p: CGPoint)scroll(to r: CGRect)
    • 只能部分地滚动,如果要实现全部的滚动,只能用UIScrollView;
    • 不过它可以结合UIImageView和UIPanGesture简单模拟UIScrollView;
    • 可以设置滚动模式为水平、垂直、二者、不可;
    scrollingViewLayer.scrollMode = kCAScrollBoth
    
    ScrollingView + UIImageView
    • 自定义Scrolling View
    import QuartzCore
    
    class ScrollingView: UIView {
      override class var layerClass : AnyClass {
        return CAScrollLayer.self
      }
    }
    
    • 手势处理
        @IBAction func panRecognized(_ sender: UIPanGestureRecognizer) {
    // 计算滚动点
            var newPoint = scrollingView.bounds.origin
            newPoint.x -= sender.translation(in: scrollingView).x
            newPoint.y -= sender.translation(in: scrollingView).y
            sender.setTranslation(CGPoint.zero, in: scrollingView)
    // 滚动到新点
            scrollingViewLayer.scroll(to: newPoint)
        }
    
    CAScrollLayer

    3. CATextLayer

    • CATextLayer是一个在指定Rect的Layer里快速绘制纯文本或富文本的layer类;
    • 它可以设置字体、字体大小、字体颜色、对齐方式、多行/单行显示模式、文本截断模式等,所有属性均可动画显示;
        let string = String.init(repeating: "这是测试CATextLayer的文字--", count: 10)
    
        textLayer.string = string // 可以是NSAttributedString
        textLayer.font = CTFontCreateWithName("Noteworthy-Light" as CFString, 0, nil)
        textLayer.fontSize = 18
        textLayer.foregroundColor = UIColor.red.cgColor // 字体颜色
        textLayer.isWrapped = true // 单行/多行
        textLayer.alignmentMode = kCAAlignmentLeft
        textLayer.truncationMode = kCATruncationEnd
        textLayer.contentsScale = UIScreen.main.scale
    
    CATextLayer

    4. AVPlayerLayer

    • AVPlayerLayer是AVFoundation库中的一个视频播放layer,用法较为简单;
    • 层级关系为AVPlayerLayer>AVPlayer>AVPlayerItem;
    var player: AVPlayer!
    
    override func viewDidLoad() {
      super.viewDidLoad()
    
      // 1
      let playerLayer = AVPlayerLayer()
      playerLayer.frame = someView.bounds
      
      // 2
      let url = Bundle.main.url(forResource: "someVideo", withExtension: "m4v")
      player = AVPlayer(url: url!)
      
      // 3
    
      // 播放完成后执行的操作--无、暂停、下一个`advance`(只适用于AVQueuePlayer)
      player.actionAtItemEnd = .none 
      playerLayer.player = player
      someView.layer.addSublayer(playerLayer)
      
      // 4
      NotificationCenter.default.addObserver(self,
                                             selector: #selector(playerDidReachEnd),
                                             name: .AVPlayerItemDidPlayToEndTime,
                                             object: player.currentItem)
    }
    
    deinit {
      NotificationCenter.default.removeObserver(self)
    }
    
    AVPlayerLayer

    5. CAGradientLayer

    • 顾名思义为渐变层,专门用来做渐变色的。
    • 用法很简单,设置一组colors、startPoint和endPoint就可以了,如果了解PS的话,相信很容易理解startPoint和endPoint;
    • 当然,它只支持线性渐变;
    func cgColor(red: CGFloat, green: CGFloat, blue: CGFloat) -> CGColor {
      return UIColor(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0).cgColor
    }
    
    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = someView.bounds
    gradientLayer.colors = [cgColor(red: 209.0, green: 0.0, blue: 0.0),
                            cgColor(red: 255.0, green: 102.0, blue: 34.0),
                            cgColor(red: 255.0, green: 218.0, blue: 33.0),
                            cgColor(red: 51.0, green: 221.0, blue: 0.0),
                            cgColor(red: 17.0, green: 51.0, blue: 204.0),
                            cgColor(red: 34.0, green: 0.0, blue: 102.0),
                            cgColor(red: 51.0, green: 0.0, blue: 68.0)]
    
    gradientLayer.startPoint = CGPoint(x: 0, y: 0)
    gradientLayer.endPoint = CGPoint(x: 0, y: 1)
    someView.layer.addSublayer(gradientLayer)
    
    • 当然你也可以通过设置locations来控制每个颜色的渐变起始位置;
    gradientLayer.locations: [NSNumber] = [:]
    
    垂直渐变 水平渐变

    6. CAReplicatorLayer

    • CAReplicatorLayer是将一个layer复制了instanceCount次,主要用来做一些动画;

    • 另外它可以设置复制间隔instanceDelay、和主要色instanceColor(针对subLayer起作用),以及复制层的颜色偏移(即过渡值),分别有instanceRedOffsetinstanceGreenOffsetinstanceBlueOffsetinstanceAlphaOffset属性;

    • 如果设置了instanceColor为whiteColor,即RGBA均为1,则instanceRedOffset设置范围为-1~0,对应颜色component的范围则是0~1

    • 最终的instanceColor即为RGBA三者offset值计算的混合色;

    // 复制器层
    replicatorLayer.backgroundColor = UIColor.clear.cgColor
    replicatorLayer.instanceCount = 30
    replicatorLayer.instanceDelay = CFTimeInterval(1 / 30.0)
    replicatorLayer.preservesDepth = false
    replicatorLayer.instanceColor = UIColor.red.cgColor
    replicatorLayer.instanceRedOffset = 0
    replicatorLayer.instanceGreenOffset = -1
    replicatorLayer.instanceBlueOffset = -1
    replicatorLayer.instanceAlphaOffset = -1 / Float(replicatorLayer.instanceCount)
    replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat.pi * 2 / 30, 0, 0, 1)
    
    // 实例层
    let layerWidth: CGFloat = 10
    let instanceLayer = CALayer()
    instanceLayer.backgroundColor = UIColor.white.cgColor
    let midX = replicatorLayer.bounds.midX - layerWidth / 2
    instanceLayer.frame = CGRect(x: midX, y: 0, width: layerWidth, height: layerWidth * 3)
    replicatorLayer.addSublayer(instanceLayer)
    
    RGBA offset = 0, -1, -1, (-1 / Float(replicatorLayer.instanceCount))
    // 动画
    let fadeAnimation = CABasicAnimation.init(keyPath: "opacity")
    fadeAnimation.fromValue = 1
    fadeAnimation.toValue = 0
    fadeAnimation.duration = 1
    fadeAnimation.repeatCount = Float(Int.max)
    instanceLayer.opacity = 0
    instanceLayer.add(fadeAnimation, forKey: "FadeAnimation")
    
    动画效果

    7. CATiledLayer

    • 瓷砖层,也叫马赛克层,相信很多人不太了解。
    • 它必将实用的一个地方是可以异步绘制,这在处理需要很占内存的视图时很有好处,比如讲一张全景photo加载到scrollView上,非常耗内存,这是可以用CATiledLayer异步绘制只在当前屏幕区域内的小图。
    • 另外它还有两个提高绘制精度的属性levelsOfDetaillevelsOfDetailBias后者是抗锯齿的级别,数值越高显示效果越细腻,单位像素点越多;
    • 使用该类要自定义view基于UIView,重写drawRect方法来实现内容的绘制,但注意不能直接设置layer.contents;
    levelsOfDetailBias = 1 levelsOfDetailBias = 5
    class TiledBackgroundView: UIView {
    
        let sideLength: CGFloat = 50
        override class var layerClass: AnyClass{
            return CATiledLayer.self
        }
        
        override func draw(_ rect: CGRect) {
            let context = UIGraphicsGetCurrentContext()
            let red = CGFloat(drand48())
            let green = CGFloat(drand48())
            let blue = CGFloat(drand48())
            context?.setFillColor(red: red, green: green, blue: blue, alpha: 1)
            context?.fill(rect)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            
            let layer = self.layer as! CATiledLayer
            let scale = UIScreen.main.scale
            layer.contentsScale = scale
            layer.tileSize = CGSize(width: sideLength * scale, height: sideLength * scale)
        }
    
    }
    
    image.png
    • 将大图切割成多个小图
    extension UIImage {
      
      class func saveTileOfSize(_ size: CGSize, name: String) -> () {
        let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] as String
        let filePath = "\(cachesPath)/\(name)_0_0.png"
        let fileManager = FileManager.default
        let fileExists = fileManager.fileExists(atPath: filePath)
            
        if fileExists == false {
          var tileSize = size
          let scale = Float(UIScreen.main.scale)
          
          if let image = UIImage(named: "\(name).jpg") {
            let imageRef = image.cgImage
            let totalColumns = Int(ceilf(Float(image.size.width / tileSize.width)) * scale)
            let totalRows = Int(ceilf(Float(image.size.height / tileSize.height)) * scale)
            let partialColumnWidth = Int(image.size.width.truncatingRemainder(dividingBy: tileSize.width))
            let partialRowHeight = Int(image.size.height.truncatingRemainder(dividingBy: tileSize.height))
            
            DispatchQueue.global(qos: .default).async {
              for y in 0..<totalRows {
                for x in 0..<totalColumns {
                  if partialRowHeight > 0 && y + 1 == totalRows {
                    tileSize.height = CGFloat(partialRowHeight)
                  }
                  
                  if partialColumnWidth > 0 && x + 1 == totalColumns {
                    tileSize.width = CGFloat(partialColumnWidth)
                  }
                  
                  let xOffset = CGFloat(x) * tileSize.width
                  let yOffset = CGFloat(y) * tileSize.height
                  let point = CGPoint(x: xOffset, y: yOffset)
                  
                  if let tileImageRef = imageRef?.cropping(to: CGRect(origin: point, size: tileSize)), let imageData = UIImagePNGRepresentation(UIImage(cgImage: tileImageRef)) {
                    let path = "\(cachesPath)/\(name)_\(x)_\(y).png"
                    try? imageData.write(to: URL(fileURLWithPath: path), options: [])
                  }
                }
              }
            }
          }
        }
      }
      
    }
    
    • 异步绘制小图
    override func draw(_ rect: CGRect) {
        let firstColumn = Int(rect.minX / sideLength)
        let lastColumn = Int(rect.maxX / sideLength)
        let firstRow = Int(rect.minY / sideLength)
        let lastRow = Int(rect.maxY / sideLength)
        
        for row in firstRow...lastRow {
            for column in firstColumn...lastColumn{
                guard let image = imageForTile(atColumn: column, row: row) else { continue }
                let x = sideLength * CGFloat(column)
                let y = sideLength * CGFloat(row)
                let tileRect = CGRect.init(x: x, y: y, width: sideLength, height: sideLength)
                image.draw(in: tileRect)
            }
        }
    }
    
    func imageForTile(atColumn column: Int, row: Int) -> UIImage? {
        let filePath = "\(cachesPath)/\(fileName)_\(column)_\(row).png"
        return UIImage(contentsOfFile: filePath)
    }
    
    异步绘制分块图像

    8. CAShapeLayer

    • 这个图形类相信大家都比较熟悉了,基于QuartzCore图形绘制库;
    • 使用方法也比较简单,建议使用PaintCode这个软件测试和学习;

    9. CATransformLayer

    • 这是一个可以transform子层的抽象layer类,对它设置backgroundColor等都不会起作用,得addSublayer才能达到想要的效果;
    • 主要通过sublayerTransform属性来重新绘制subLayers,已达到3D变换的效果;
    • 它也没法接受events,只能通过检测对sublayer的event来处理touch等事件;
    CATransformLayer

    创建六面体

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 前
        var layer = sideLayer(color: .red)
        transformLayer.addSublayer(layer)
        layer.transform = CATransform3DMakeTranslation(0.0, 0.0, sideLength / 2)
        
        // 后
        layer = sideLayer(color: .green)
        transformLayer.addSublayer(layer)
        layer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength / 2)
        
        // 上
        layer = sideLayer(color: .orange)
        transformLayer.addSublayer(layer)
        var transform = CATransform3DMakeTranslation(0.0, sideLength / 2, 0.0)
        transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0)
        layer.transform = transform
        
        // 下
        layer = sideLayer(color: .blue)
        transformLayer.addSublayer(layer)
        transform = CATransform3DMakeTranslation(0.0, -sideLength / 2, 0.0)
        transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0)
        layer.transform = transform
        
        // 左
        layer = sideLayer(color: .cyan)
        transformLayer.addSublayer(layer)
        transform = CATransform3DMakeTranslation(-sideLength / 2, 0.0, 0.0)
        transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0)
        layer.transform = transform
        
        // 右
        layer = sideLayer(color: .purple)
        transformLayer.addSublayer(layer)
        transform = CATransform3DMakeTranslation(sideLength / 2, 0.0, 0.0)
        transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0)
        layer.transform = transform
        
        rotate(xOffset: 200, yOffset: 200)
    }
    
    func sideLayer(color: UIColor) -> CALayer {
        let layer = CALayer()
        layer.backgroundColor =
            color.withAlphaComponent(0.6).cgColor
        layer.frame = CGRect(origin: .zero, size: CGSize(width: sideLength, height: sideLength))
        layer.position = CGPoint(x: transformLayerView.bounds.midX, y: transformLayerView.bounds.midY)
        return layer
    }
    
    func degreesToRadians(_ degrees: Double) -> CGFloat {
        return CGFloat(degrees * .pi / 180.0)
    }
    

    旋转变换

    func rotate(xOffset: Double, yOffset: Double) {
        let totalOffset = sqrt(xOffset * xOffset + yOffset * yOffset)
        let totalRotation = CGFloat(totalOffset * .pi / 180.0)
        let xRotationalFactor = CGFloat(totalOffset) / totalRotation
        let yRotationalFactor = CGFloat(totalOffset) / totalRotation
        let currentTransform = CATransform3DTranslate(transformLayer.sublayerTransform, 0.0, 0.0, 0.0)
        let x = xRotationalFactor * currentTransform.m12 - yRotationalFactor * currentTransform.m11
        let y = xRotationalFactor * currentTransform.m22 - yRotationalFactor * currentTransform.m21
        let z = xRotationalFactor * currentTransform.m32 - yRotationalFactor * currentTransform.m31
        let rotation = CATransform3DRotate(transformLayer.sublayerTransform, totalRotation, x, y, z)
        transformLayer.sublayerTransform = rotation
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let location = touches.first?.location(in: transformLayerView) else {
            return
        }
        rotate(xOffset: Double(location.x / 50), yOffset: Double(location.y / 50))
    }
    

    hitTest

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let location = touches.first?.location(in: transformLayerView) else {
            return
        }
        for layer in transformLayer.sublayers! where layer.hitTest(location) != nil {
            print("Touched Sublayer")
        }
    }
    

    10. CAEmitterLayer

    • 顾名思义,CAEmitterLayer主要配合CAEmitterCell来发射粒子,比较高效;
    • 设置emitterCells属性可以在一个layer上添加多个cell发射器;
    • 可以设置emitterPosition设置发射器的位置;

    setupEmitterLayer

    func setupEmitterLayer() {
        emitterLayer.emitterCells = [emitterCell]
        emitterLayer.seed = UInt32(Date().timeIntervalSince1970)
        emitterLayer.renderMode = kCAEmitterLayerAdditive
        emitterLayer.drawsAsynchronously = true
        setEmitterPosition(CGPoint(x: view.bounds.midX, y: view.bounds.midY))
    }
    

    setupEmitterCell

    func setupEmitterCell(){
        emitterCell.contents = UIImage.init(named: "smallStar")?.cgImage
        
        emitterCell.velocity = 50.0
        emitterCell.velocityRange = 500.0
        
        emitterCell.color = UIColor.black.cgColor
        emitterCell.redRange = 1.0
        emitterCell.greenRange = 1.0
        emitterCell.blueRange = 1.0
        emitterCell.alphaRange = 0.0
        emitterCell.redSpeed = 0.0
        emitterCell.greenSpeed = 0.0
        emitterCell.blueSpeed = 0.0
        emitterCell.alphaSpeed = -0.5
        
        let zeroDegreesInRadians = degreesToRadians(0.0)
        emitterCell.spin = degreesToRadians(130.0)
        emitterCell.spinRange = zeroDegreesInRadians
        emitterCell.emissionRange = degreesToRadians(360.0)
        
        emitterCell.lifetime = 1.0
        emitterCell.birthRate = 250.0
        emitterCell.xAcceleration = -800.0
        emitterCell.yAcceleration = 1000.0
    }
    

    其他设置

    func setEmitterPosition(_ position: CGPoint) {
        emitterLayer.emitterPosition = position
    }
    
    func degreesToRadians(_ degrees: Double) -> CGFloat {
        return CGFloat(degrees * .pi / 180.0)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let location = touches.first?.location(in: view) else {
            return
        }
        setEmitterPosition(location)
    }
    
    CAEmitterLayer

    以上就是全部的CALayer相关的知识了,工程文件见https://github.com/BackWorld/LayerPlayer

    如果对你有帮助,别忘了点个👍或关注下哦~

    相关文章

      网友评论

        本文标题:iOS Review | 你所不知道的10种Layer

        本文链接:https://www.haomeiwen.com/subject/gpwrsxtx.html