【问题标题】:Removing all CALayer's sublayers移除所有 CALayer 的子层
【发布时间】:2011-01-05 06:30:56
【问题描述】:

我无法删除所有图层的子图层。我目前手动执行此操作,但这会带来不必要的混乱。我在 google 上找到了很多关于此的主题,但没有答案。

我试图做这样的事情:

for(CALayer *layer in rootLayer.sublayers) { [层 removeFromSublayer]; }

但是没有用。

另外,我尝试将 rootLayer.sublayers 克隆到单独的 NSArray 中,但结果是一样的。

有什么想法吗?

编辑:

我认为它现在有效,但我错了。它适用于 CALayers,但不适用于 CATextLayers。有什么想法吗?

【问题讨论】:

    标签: objective-c cocoa core-animation


    【解决方案1】:

    您可以简单地为您添加的 CAlayer 提供一个标识符,然后通过搜索将其删除。像这样

     let spinLayer = CAShapeLayer()
     spinLayer.path = UIBezierPath()
     spinLayer.name = "spinLayer"
    
    
      func removeAnimation() {
         for layer in progressView.layer.sublayers ?? [CALayer]()
             where layer.name == "spinLayer" {
               layer.removeFromSuperlayer()
           }
       }
    

    【讨论】:

      【解决方案2】:

      如果UIView 包含任何子视图,则设置self.view.layer.sublayers = nil[layer removeFromSuperlayer] 都会导致崩溃。从 superLayer 中删除 CALayer 的最佳方法是维护一个层数组。 例如该数组是arrayOfLayers,

      每次你在UIView.sublayer上添加一个层时,在这个数组中添加那个层...

      例如

      [arrayOfLayers addObject:layer];
      [self.view.layer addSublayer:layer];
      

      删除时只调用这个,

      [arrayOfLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
      

      【讨论】:

        【解决方案3】:

        我也遇到过同样的问题,找到了很多解决方案,但没有一个是完美的。
        终于从above answer of pnavk 我明白了。那里的代码适用于 Xamarin/C# 用户。
        iOS 版本可能如下:

        斯威夫特 2.3

            if rootLayer.sublayers?.count > 0 {
                rootLayer.sublayers?.forEach {
                    if $0.name == "bottomBorderLayer" {
                        $0.removeFromSuperlayer()
                    }
                }
            }
        
            let border = CALayer()
            let height = CGFloat(1.0)
        
            border.borderColor = UIColor.blackColor().CGColor
            border.borderWidth = height
        
            border.frame = CGRectMake(0, self.frame.size.height - height, self.frame.size.width, self.frame.size.height)
        
            border.name = "bottomBorderLayer"
            rootLayer.addSublayer(border)
            rootLayer.masksToBounds = true
        


        Objective-C

        if (rootLayer.sublayers.count > 0) {
            for (CALayer *layer in rootLayer.sublayers) {
                if ([layer.name isEqualToString:@"bottomBorderLayer"]) {
                    [layer removeFromSuperlayer];
                }
            }
        }
        
        CALayer *border = [[CALayer alloc] init];
        CGFloat height = 1.0;
        
        border.borderColor = [UIColor blackColor].CGColor;
        border.borderWidth = height;
        
        border.frame = CGRectMake(0, self.view.frame.size.height - height, self.view.frame.size.width, self.view.frame.size.height);
        border.name = @"bottomBorderLayer";
        [rootLayer addSublayer:border];
        rootLayer.masksToBounds = TRUE;
        

        以上代码仅适用于底部边框。您可以根据需要更改边框。
        在向控制器添加任何层之前,我刚刚运行了一个 for 循环来检查是否已经应用了任何边框?
        为了识别之前添加的边框,我使用了 CALayer 的 name 属性。并在从子层中删除之前比较该层。
        我在使用 name 属性之前尝试过相同的代码,但它会产生随机崩溃。但是在使用 name 属性并在 remove 之前比较 name 之后将解决崩溃问题。

        我希望这会对某人有所帮助。

        【讨论】:

          【解决方案4】:

          怎么办:

          rootLayer.sublayers = @[];
          

          【讨论】:

            【解决方案5】:

            对于swift3+,你可以用这个。

            yourView.layer.sublayers?.removeAll(keepingCapacity: true)

            请看这里:Apple API Reference

            或者您可以: yourView.layer.sublayers?.forEach{ $0.removeFromSuperlayer()}

            【讨论】:

            • 不幸的是,这两个答案做了两个不同的事情。第一个实际上清空了数组结构,而第二个从其超层中删除了每一层。即使您运行的是 Swift 3+,我也不推荐第一个。
            【解决方案6】:

            Swift(短版):

            layer.sublayers?.forEach { $0.removeFromSuperlayer() }

            【讨论】:

            • 不错的一个!谢谢!
            【解决方案7】:

            我必须在 Xamarin/C# 中执行此操作。我有一个UITableViewCell 和一些CAShapeLayers 用于边框。以上所有选项(包括复制阵列然后删除层导致崩溃)。对我有用的方法:

            添加CALayer时,我给它起了个名字:

                var border = new CALayer();
                border.BackgroundColor = color.CGColor;
                border.Frame = new  CGRect(x, y, width, height);
                border.Name = "borderLayerName";
                Layer.AddSublayer(border);
            

            UITableViewCell 上的PrepareForReuse 中,我创建了SubLayers 属性的副本并删除了与我之前指定的名称匹配的所有内容:

                CALayer[] copy = new CALayer[Layer.Sublayers.Length];
                Layer.Sublayers.CopyTo(copy, 0);
                copy.FirstOrDefault(l => l.Name == "borderLayerName")?.RemoveFromSuperLayer();
            

            没有崩溃。

            希望这会有所帮助!

            【讨论】:

            • 这对我帮助很大。谢谢朋友。
            • 我认为这是最安全的 - 在删除尚未添加的图层时避免错误
            【解决方案8】:

            调用 rootLayer.sublayers = nil; 可能会导致崩溃(例如,如果在 iOS 8 下,您在拥有 rootLayer 的视图上调用 removeFromSuperview 两次)。

            正确的方法应该是:

            [[rootLayer.sublayers copy] makeObjectsPerformSelector:@selector(removeFromSuperlayer)]

            需要调用copy,这样迭代调用removeFromSuperlayer的数组就不会被修改,否则会引发异常。

            【讨论】:

              【解决方案9】:

              当然可以:
              self.layer.sublayers=nil;
              正如帕斯卡布尔克所建议的那样。但最好调用子层属性的设置器:
              [self.layer setSublayers:nil];
              避免任何清理问题(如果有的话)。

              【讨论】:

              • 实际上,这两行代码生成的代码完全相同。点符号只是调用 getter 和 setter 方法的语法糖。也许您认为这些是实例变量?
              【解决方案10】:

              我试图只删除索引 0 处的第一个子层,这很有效:

              - (UITableViewCell *)tableView:(UITableView *)tableView
                       cellForRowAtIndexPath:(NSIndexPath *)indexPath {
              
                  if ([cell.contentView.layer.sublayers count] != 0) {
                      [[cell.contentView.layer.sublayers objectAtIndex:0] removeFromSuperlayer];
                  }
              

              【讨论】:

                【解决方案11】:

                不加选择地删除所有子层会在 iOS 7 中导致严重的崩溃,这可能会在程序执行的更晚发生。我已经使用rootLayer.sublayers = nil[rootLayer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)] 彻底测试了这一点。一定有一个系统创建的层搞砸了。

                您必须保留自己的图层数组并自己删除它们:

                [myArrayOfLayersIAddedMyself makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
                

                【讨论】:

                • 这很重要,删除所有子层可能很危险!除了@Nestor 的回答之外,另一种方法是使用 CALayer 的属性名称来跟踪您添加的那些:shapeLayer.name = linesLayerName;。最后只删除有你名字的图层:for (CALayer *layer in [self.layer.sublayers copy]) { if ([[layer name] isEqualToString:linesLayerName]) { [layer removeFromSuperlayer]; } }
                【解决方案12】:
                [rootLayer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
                

                【讨论】:

                • 这确实有效,但我很好奇为什么。类 ref 表示 makeObjectsPerformSelector: 中发送的选择器方法“不能有修改接收数组的副作用”。但是removeFromSuperlayer 不是同时修改了数组中的对象(它们的超层属性无效)和数组本身(它失去了它的成员)吗?
                • 我在尝试这个时收到Collection <CALayerArray: 0x60800005e3f0> was mutated while being enumerated.
                • 我强烈建议先复制子层,然后按照上面的答案进行删除,否则它会像@dbainbridge所说的那样崩溃。
                【解决方案13】:

                从图层中删除所有子图层的最简单方法是将 sublayer 属性设置为 nil:

                rootLayer.sublayers = nil;

                【讨论】:

                • 这会自动释放其中的层吗?
                • 据我所知,是的(当然,它只是释放子层,只有在没有其他东西保留它们的情况下才会释放它们)。
                • 我很高兴这个解决方案没有触发任何“集合在被枚举时发生变异”异常,但我担心子层的superlayer 属性可能不会改变——对我来说是个问题因为这些层由另一个控制器拥有。我通过暂时保留对其中一个子层的引用(通过sublayers objectAtIndex:0)对此进行了测试,并且可以确认其superlayer 属性确实无效。
                • 这完全会导致崩溃和混乱。
                • 不要这样做。它会在事件发生后导致随机崩溃。
                【解决方案14】:

                使用反向枚举怎么样?

                NSEnumerator *enumerator = [rootLayer.sublayers reverseObjectEnumerator];
                for(CALayer *layer in enumerator) {
                    [layer removeFromSuperlayer];
                }
                

                因为子层中的分组在枚举过程中发生了变化,如果顺序正常的话。 我想知道上面代码的结果。

                【讨论】:

                • 根据 Apple 的文档,不要这样做:“枚举是‘安全的’——枚举器有一个突变保护,因此如果您在枚举期间尝试修改集合,则会引发异常。”我不知道它为什么会起作用,但我会使用 Ben 发布的方法。
                • 我想知道之前子层中的层实际上是使用Ben发布的方法释放的。正如大卫所说,快速枚举禁止修改其集合。
                • 我不知道为什么它被否决了 - 它是一种在执行删除操作时通过数组反向计数的标准技术。
                • 这就像一个魅力。我不知道反向枚举使删除安全,但现在我会一直使用它!
                • 好吧,我错了。这只有效,因为 layer.sublayers 是一个 IMMUTABLE 数组,并且“删除”由“removeFromSuperlayer” - 方法处理。它仍然是一个不错的选择,但它不像我可以在执行删除时突然使用带有可变数组的 forin 循环。所以回到简单的for.loops,我猜;(
                【解决方案15】:

                以下应该有效:

                for (CALayer *layer in [[rootLayer.sublayers copy] autorelease]) {
                    [layer removeFromSuperlayer];
                }
                

                【讨论】:

                • 谢谢!您的解决方案有效,我的可能也有效,因为问题出在其他地方。
                • 您必须使用子层列表的副本:否则您最终会在迭代列表时修改列表,这并不是很安全。
                • 我最喜欢的通过可能删除对象来改变数组的方法是使用 for 循环向后遍历它: for (int i = rootLayer.sublayers.count-1; i >= 0; i-- ) { [[rootLayer.sublayers objectAtIndex:i] removeFromSuperlayer]; }
                • 我在使用这种技术时遇到了一些随机崩溃。当包含子层的主 UIView 被留空然后被点击时,它崩溃了,因为没有层可以 hitTest。因此,避免这种情况的一个简单解决方案是将 UIView 的框架设置为 CGRectZero
                猜你喜欢
                • 2012-09-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多