ma11hew28
ma11hew28

Reputation: 126507

iOS Core Animation: CALayer bringSublayerToFront?

How do I bring a CALayer sublayer to the front of all sublayers, analogous to -[UIView bringSubviewToFront]?

Upvotes: 41

Views: 32534

Answers (8)

Vlad
Vlad

Reputation: 6730

Swift 4 version.
Idea that layer itself has bringToFront and sendToBack methods.

#if os(iOS)
   import UIKit
#elseif os(OSX)
   import AppKit
#endif

extension CALayer {

   func bringToFront() {
      guard let sLayer = superlayer else {
         return
      }
      removeFromSuperlayer()
      sLayer.insertSublayer(self, at: UInt32(sLayer.sublayers?.count ?? 0))
   }

   func sendToBack() {
      guard let sLayer = superlayer else {
         return
      }
      removeFromSuperlayer()
      sLayer.insertSublayer(self, at: 0)
   }
}

Usage:

let view = NSView(frame: ...)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.gray.cgColor

let l1 = CALayer(...)
let l2 = CALayer(...)

view.layer?.addSublayer(l1)
view.layer?.addSublayer(l2)

l1.bringToFront()

Upvotes: 9

Javier Flores Font
Javier Flores Font

Reputation: 2093

Create a category of CALayer like this:

@interface CALayer (Utils)

- (void)bringSublayerToFront;

@end

@implementation CALayer (Utils)

- (void)bringSublayerToFront {
    CGFloat maxZPosition = 0;  // The higher the value, the closer it is to the front. By default is 0.
    for (CALayer *layer in self.superlayer.sublayers) {
        maxZPosition = (layer.zPosition > maxZPosition) ? layer.zPosition : maxZPosition;
    }
    self.zPosition = maxZPosition + 1;
}

@end

Upvotes: 3

Andrei Marincas
Andrei Marincas

Reputation: 456

You can implement this functionality in a category on CALayer like so:

CALayer+Extension.h

#import <QuartzCore/QuartzCore.h>

typedef void (^ActionsBlock)(void);

@interface CALayer (Extension)

+ (void)performWithoutAnimation:(ActionsBlock)actionsWithoutAnimation;
- (void)bringSublayerToFront:(CALayer *)layer;

@end

CALayer+Extension.m

#import "CALayer+Extension.h"

@implementation CALayer (Extension)

+ (void)performWithoutAnimation:(ActionsBlock)actionsWithoutAnimation
{
    if (actionsWithoutAnimation)
    {
        // Wrap actions in a transaction block to avoid implicit animations.
        [CATransaction begin];
        [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

        actionsWithoutAnimation();

        [CATransaction commit];
    }
}

- (void)bringSublayerToFront:(CALayer *)layer
{
    // Bring to front only if already in this layer's hierarchy.
    if ([layer superlayer] == self)
    {
        [CALayer performWithoutAnimation:^{

            // Add 'layer' to the end of the receiver's sublayers array.
            // If 'layer' already has a superlayer, it will be removed before being added.
            [self addSublayer:layer];
        }];
    }
}

@end

And for easy access you can #import "CALayer+Extension.h" in your project's Prefix.pch (precompiled header) file.

Upvotes: 2

ivanzoid
ivanzoid

Reputation: 6072

This is variation of @MattDiPasquale's implementation which reflects UIView's logic more precisely:

- (void) bringSublayerToFront:(CALayer *)layer
{
    [layer removeFromSuperlayer];
    [self insertSublayer:layer atIndex:[self.sublayers count]];
}

- (void) sendSublayerToBack:(CALayer *)layer
{
    [layer removeFromSuperlayer];
    [self insertSublayer:layer atIndex:0];
}

Note: if you don't use ARC, you may wish to add [layer retain] at top and [layer release] at bottom of both functions to make sure layer is not accidentally destructed in a case it has retain count = 1.

Upvotes: 44

Shaun Budhram
Shaun Budhram

Reputation: 3710

I'm curious why none of these answers mention the zPosition attribute on CALayer. Core Animation looks at this attribute to figure out layer rendering order. The higher the value, the closer it is to the front. These answers all work as long as your zPosition is 0, but to easily bring a layer to the front, set its zPosition value higher than all other sublayers.

Upvotes: 58

CodenameLambda1
CodenameLambda1

Reputation: 1299

This is the correct code:

-(void)bringSubLayerToFront:(CALayer*)layer
{
  [layer.superLayer addSubLayer:layer];
}

-(void)sendSubLayerToBack:(CALayer*)layer
{
  [layer.superlayer insertSublayer:layer atIndex:0];
}

Upvotes: 2

Tim Kozak
Tim Kozak

Reputation: 4182

Right code here

- (void)bringSublayerToFront:(CALayer *)layer {
    CALayer *superlayer = layer.superlayer;
    [layer removeFromSuperlayer];
    [superlayer insertSublayer:layer atIndex:[superlayer.sublayers count]];
}

- (void)sendSublayerToBack:(CALayer *)layer {
    CALayer *superlayer = layer.superlayer;
    [layer removeFromSuperlayer];
    [superlayer insertSublayer:layer atIndex:0];
}

Upvotes: 8

ma11hew28
ma11hew28

Reputation: 126507

I don't think such methods exist, but it's easy to roll your own.

// CALayer+Additions.h

@interface CALayer (Additions)
- (void)bringSublayerToFront:(CALayer *)layer;
- (void)sendSublayerToBack:(CALayer *)layer;
@end

// CALayer+Additions.m

@implementation CALayer (Additions)

- (void)bringSublayerToFront:(CALayer *)layer {
    CALayer *superlayer = self.superlayer;
    [self removeFromSuperlayer];
    [superlayer insertSublayer:gradientLayer atIndex:[self.sublayers count]-1];
}

- (void)sendSublayerToBack:(CALayer *)layer {
    CALayer *superlayer = self.superlayer;
    [self removeFromSuperlayer];
    [superlayer insertSublayer:gradientLayer atIndex:0];
}

Upvotes: -6

Related Questions