12

How can you make an NSArray full of multiple instances of a CALayer (all with the same frame, contents etc)?

Background: CALayer takes a bit of overhead to create, so I would like to create a number of CALayers (all sharing the same properties) in the init method of a class (to be used later on in that class.)

Alex Zavatone
  • 4,106
  • 36
  • 54
Reeds
  • 211
  • 1
  • 3
  • 12
  • Why do you need to copy them? Why not create them all from scratch or, reduce the number of layers your app needs? – Rog Mar 29 '09 at 17:41
  • yes, that's an option (creating them all from scratch), but its not terribly elegant – Reeds Mar 29 '09 at 18:15

7 Answers7

25

I haven't tried this with CALayer specifically, but I know you can perform a deep-copy by taking advantage of NSCoding:

CALayer *layer = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:layer]];

I'm not sure how copying them would really help with performance, though.

MaxGabriel
  • 7,617
  • 4
  • 35
  • 82
6

CALayer doesn't have a built in -(id)copy method. I'm not sure why. It's not difficult to gin up your own however. Create a CALayer category and write your own copy method. All you have to do is instantiate and manually get the public ivars/properties from the original and set to the new copy. Don't forget to call [super copy]

BTW, CALayer is an object. You can add it to an NSArray.

amattn
  • 10,045
  • 1
  • 36
  • 33
2

Try to use CAReplicatorLayer. It can duplicate your layers.

reference: https://developer.apple.com/documentation/quartzcore/careplicatorlayer

sample code: http://www.knowstack.com/swift-careplicatorlayer-sample-code/ https://developer.apple.com/documentation/quartzcore/careplicatorlayer/1522391-instancedelay

Alex Zavatone
  • 4,106
  • 36
  • 54
ossamacpp
  • 656
  • 5
  • 11
1

An updated link to MaxGabriel's top rated answer.

Objective-C

CALayer *layer1;
CALayer *layer2;

// Set up layer1's specifics.

layer2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[CALayer class]
                                                            fromData:[NSKeyedArchiver archivedDataWithRootObject:layer1 requiringSecureCoding:NO error:nil] error:nil];

And in Swift.

let layer1: CALayer?
var layer2: CALayer? = nil
// Set up layer1's specifics

do {
    layer2 = try NSKeyedUnarchiver.unarchivedObject(
        ofClass: CALayer.self,
        from: try NSKeyedArchiver.archivedData(withRootObject: layer1, requiringSecureCoding: false))
} catch {
// It failed.  Do something. 
}
Alex Zavatone
  • 4,106
  • 36
  • 54
0

I do exactly the same thing in my program.

In init:

    self.turrets = [NSMutableArray array];
    for (count = 0; count < kMaxTurrets; count++)
        [self spawnTurret];

spawnTurret:

evTurret* aTurret = [[[evTurret alloc] init] autorelease];
CGImageRef theImage = [self turretContents];
aTurret.contents = theImage;
double imageHeight = CGImageGetHeight(theImage);
double imageWidth = CGImageGetWidth(theImage);
double turretSize = 0.06*(mapLayer.bounds.size.width + mapLayer.bounds.size.height)/2.0;
aTurret.bounds = CGRectMake(-turretSize*0.5, turretSize*0.5, turretSize*(imageWidth/imageHeight), turretSize);
aTurret.hidden = YES;
[mapLayer addSublayer:aTurret]; 
[self.turrets addObject:aTurret];

Basically, just I just repeatedly create CALayer objects. It's going to be faster than copying them, as this method only requires 1 CALayer call per property, as opposed to copying it which requires you to read the property and then additionally set it. I spawn about 500 objects using this method in about 0.02 seconds, so it's definitely fast. If you really needed more speed you could even cache the image file.

Charliehorse
  • 313
  • 2
  • 8
0

NSProxy is used for that reason. What you're describing is a common scenario, and one from which any number of design patterns are derived.

Pro Objective-C Design Patterns for iOS provides the solution to the very problem you describe; read Chapter 3: The Protoype Pattern. Here's a summary definition:

The Prototype pattern specifies the kind of objects to create using a prototypical instance, whereby a new object is created by copying this instan

James Bush
  • 1,485
  • 14
  • 19
0

Although it's best to use CAReplicatorLayer for your use case,
others may really need to copy CALayer, so I'm here writing another answer.
(I had to copy because of CARenderer)

I tried NSKeyedArchiver as well, it works mostly, but some functionality get broken for complex CALayer (for me, it was the mask with filters)

implementation

Implementation is pretty straightforward.

  1. Make new CALayer.
  2. Copy properties that you need/use.
  3. Do it recursively for sublayers and mask.

Here is an example for CAShapeLayer:

extension CALayer {
    func deepCopy() -> CAShapeLayer {
        let layer = CAShapeLayer()
        layer.frame = frame
        layer.bounds = bounds
        layer.filters = filters
        layer.contents = contents
        layer.contentsScale = contentsScale
        layer.masksToBounds = masksToBounds
        //add or remove lines for your need

        if let self = self as? CAShapeLayer {
            layer.path = self.path?.copy()
            layer.lineCap = self.lineCap
            layer.lineJoin = self.lineJoin
            layer.lineWidth = self.lineWidth
            layer.fillColor = self.fillColor
            layer.strokeColor = self.strokeColor
            //add or remove lines for your need
        }

        layer.mask = mask?.deepCopy()
        layer.sublayers = sublayers?.map { $0.deepCopy() }
        return layer
    }
}
eastriver lee
  • 173
  • 2
  • 9