【问题标题】:iOS Core Animation: CALayer bringSublayerToFront?iOS 核心动画:CALayer 带来SublayerToFront?
【发布时间】:2011-09-26 17:06:36
【问题描述】:

如何将CALayer sublayer 放在所有子层的前面,类似于-[UIView bringSubviewToFront]?

【问题讨论】:

    标签: iphone objective-c ios core-animation calayer


    【解决方案1】:

    Swift 4 版本。
    想法层本身有bringToFront 和sendToBack 方法。

    #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)
       }
    }
    

    用法:

    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()
    

    【讨论】:

      【解决方案2】:

      像这样创建一个 CALayer 类别:

      @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
      

      【讨论】:

        【解决方案3】:

        您可以在 CALayer 上的类别中实现此功能,如下所示:

        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
        

        为了方便访问,您可以在项目的 Prefix.pch(预编译头文件)文件中#import "CALayer+Extension.h"。

        【讨论】:

          【解决方案4】:

          这是@MattDiPasquale 实现的变体,更准确地反映了 UIView 的逻辑:

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

          注意:如果您不使用 ARC,您可能希望在两个函数的顶部添加 [layer retain] 并在底部添加 [layer release] 以确保 layer 不会被意外破坏保留计数 = 1 的案例。

          【讨论】:

          • removeFromSuperlayer 方法是不必要的,因为它会在调用insertSublayer 时自动删除。
          • removeFromSuperlayer 的另一个问题:如果 layer 是唯一的子层,删除它显然会导致 sublayers 数组为零。在 Swift 中,这会导致 self.sublayers 上的选项展开崩溃。 (在 Obj-C 上可能没问题,因为 [nil count] 可能返回 0,这实际上有效)。
          【解决方案5】:

          我很好奇为什么这些答案都没有提到 CALayer 上的 zPosition 属性。 Core Animation 会查看这个属性来确定图层的渲染顺序。值越高,越靠近前面。只要您的 zPosition 为 0,这些答案都有效,但要轻松地将层置于前面,请将其 zPosition 值设置为高于所有其他子层。

          【讨论】:

          • 我完全忘记了 zPosition,这个答案是最好的。两个答案都有效,但这个更简单和可靠的 IMO
          • 来自Apple documentation:“当在另一个子图层上方或下方插入时,您只需指定子图层在子图层数组中的位置。图层的实际可见性主要由其 zPosition 中的值决定属性,其次是它们在子层数组中的位置。”
          【解决方案6】:

          这是正确的代码:

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

          【讨论】:

            【解决方案7】:

            这里有正确的代码

            - (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];
            }
            

            【讨论】:

              【解决方案8】:

              我不认为这样的方法存在,但很容易自己动手。

              // 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];
              }
              

              【讨论】:

              • 有没有机会解释为什么有人对此投了反对票?
              • 可能是因为代码错误。不使用参数层。 gradientLayer 未定义。不需要 superlayer 调用,因为 layer 参数的 superlayer 是 self。 ivanzoid 已发布更正的代码。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-16
              • 1970-01-01
              • 2015-01-24
              • 1970-01-01
              相关资源
              最近更新 更多