1

I'm a newbie to the iOS development and I'm trying to figure out how CATiledLayer works. NOT how to use it!

There are two key features of CATiledLayer I want to know how it works.

  1. Visible bounds automatic reporting. How does CATiledLayer know its visible bounds have changed? Can I create a custom CALayer and register to know the same information?

  2. Tile drawing. How are these tiles drawn? Are they drawn as sublayers?

The reason I want to find out how these two key points work is that I'm building a custom UIView that can have a very large width and a CALayer will crash due to having such a large backed layer. CATiledLayer has a light memory usage and its' almost perfect!

The reason I don't want to use CATiledLayer is how it works internally. All drawing happens on a background thread. I know you can even have the drawing logic be called on the main thread but this would happen on the next drawing cycle. Because drawing doesn't happen on the same draw cycle, during a view resize our drawing logic has a delay updating the drawings thus causing the UIView content to shake during updates by the user.

Just to add a little more to this. I'm building an audio editor where it shows the audio waveform and the user can resize this clip. The clip is shown inside a collection view. The mentioned issue above with CATiledLayer seems to be the same issue Garage band has. I can barely notice it when resizing an audio clip to the left. They are likely using CATiledLayer.

Cocoatype
  • 2,572
  • 25
  • 42
Jona
  • 13,325
  • 15
  • 86
  • 129
  • Lots of info but is there a way to show a video of what you built and what you are trying to do. I have an audio waveform in an app I have done as well. – agibson007 May 09 '18 at 21:42
  • @agibson007 I actually posted before something about this subject and now I'm asking again for slightly differently. On this post you should be able to see some screenshots of what I got. I'll try to put a video here too. https://stackoverflow.com/questions/48707815/how-to-draw-parts-of-a-uiview-as-it-becomes-visible-inside-uicollectionview – Jona May 09 '18 at 23:42
  • Is the drawing clogging the ui to draw it at a single time without the CATiledLayer. I changed ours to use a single CAShapeLayer. Thanks for the info. I have an idea to notify so give me a bit to test – agibson007 May 09 '18 at 23:47
  • @agibson007 sorry my answer to your question was horribly written. :P So drawing has to be optimized to chunks that are visible on the collectionview otherwise large audio clips would make things very slow. Also, CALayer backed UIView crashes when it's too large. Thanks for helping out! – Jona May 09 '18 at 23:51

1 Answers1

1

I know one way to do question number one but I am not sure of the consequences and efficiency. This is more theoretical except it works. I am using a CADisplayLink to run a check to see if the frame of the layer is in the main window. I did notice a small bit of CPU (1% or less) being used so I would test it more compared to the CATiledLayer. CATiledLayer just breaks the drawing up but operates on the same premise that only what is visible can be drawn. drawRect I think fundamentally works when visible or the visible bounds change. As far as subclass I tested I used it inside a UICollectionView and know that it works. I could even get logs of when a cell was created and not on screen. Here is the working subclass of CALayer. I don't know if this helps you but it is possible.

import UIKit
protocol OnScreenLayerDelegate:class {
    func layerIsOnScreen(currentScreenSpace:CGRect)
    func layerIsNotOnScreen(currentScreenSpace:CGRect)
}
class OnScreenLayer: CALayer {

    var displayLink : CADisplayLink?
    weak var onScreenDelegate : OnScreenLayerDelegate?
    override init() {
        super.init()
        commonSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonSetup()
    }

    func commonSetup(){
        displayLink = CADisplayLink(target: self, selector: #selector(checkOnScreen))
        displayLink?.add(to: .main, forMode: .commonModes)
    }

    @objc func checkOnScreen(){
        if let window = UIApplication.shared.keyWindow{
            let currentRect = self.convert(self.bounds, to: window.layer)
            if window.bounds.intersects(currentRect){
                onScreenDelegate?.layerIsOnScreen(currentScreenSpace: currentRect)
            }else{
                onScreenDelegate?.layerIsNotOnScreen(currentScreenSpace: currentRect)
            }
        }
    }
}

As to question 2 I think CATiledLayers probably does not use sublayers and instead slowly draws all the contents into a single contents image but by tiling and probably easier math for them. It might be something that takes the visible area draws it in the background and provides the layer contents. Then caches that section and adds another until it is complete.This is only a guess.

Here is the code from my test in a collectionView cell.

import UIKit

class AlbumCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var imageView: UIImageView!
    var onScreenLayer = OnScreenLayer()
    var currentIndex : Int = 0
    override func awakeFromNib() {
        super.awakeFromNib()
        onScreenLayer.frame = self.bounds
        self.layer.addSublayer(onScreenLayer)
        onScreenLayer.onScreenDelegate = self
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        onScreenLayer.frame = self.bounds
    }
}

extension AlbumCollectionViewCell : OnScreenLayerDelegate{
    func layerIsOnScreen(currentScreenSpace: CGRect) {
        //or more proof
         print("it is on screen \(currentIndex)")

    }

    func layerIsNotOnScreen(currentScreenSpace: CGRect) {
        print("not on screen \(currentIndex)")
    }
}

The current index was set in cellForItem so I could monitor it. Let me know if this helps. Also the check could modify the frame to catch it right before it comes on screen by a margin that way you are drawing prior to that.

agibson007
  • 4,173
  • 2
  • 19
  • 24
  • Using CADisplayLink is an interesting idea. Almost feels wrong but I'm going to explore a bit with your idea here. I'll write back with whatever I find. Thanks for taking the time to help out! :) – Jona May 11 '18 at 18:01
  • @Jona I don’t disagree that it feels wrong just know that it works – agibson007 May 11 '18 at 19:09