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.