【问题标题】:How do I draw a shadow under a UIView?如何在 UIView 下绘制阴影?
【发布时间】:2010-10-22 19:05:55
【问题描述】:

我正在尝试在 Cocoa Touch 中的 UIView 的底部边缘下绘制阴影。我明白应该用CGContextSetShadow()来画阴影,但是Quartz 2D编程指南有点含糊:

  1. 保存图形状态。
  2. 调用函数CGContextSetShadow,传递适当的值。
  3. 执行所有要应用阴影的绘图。
  4. 恢复图形状态

我在 UIView 子类中尝试了以下内容:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

..但这对我不起作用,我有点纠结于 (a) 下一步要去哪里和 (b) 如果我需要对我的UIView 做任何事情来完成这项工作?

【问题讨论】:

    标签: iphone objective-c ios cocoa-touch core-graphics


    【解决方案1】:

    在您当前的代码中,您保存当前上下文的GState,将其配置为绘制阴影.. 并将其恢复到配置为绘制阴影之前的状态。然后,最后,您调用超类的drawRect 实现:。

    任何应该受阴影设置影响的绘图都需要发生在之后

    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    

    之前

    CGContextRestoreGState(currentContext);
    

    因此,如果您希望超类的 drawRect: 被“包裹”在阴影中,那么如果您像这样重新排列代码会怎样?

    - (void)drawRect:(CGRect)rect {
        CGContextRef currentContext = UIGraphicsGetCurrentContext();
        CGContextSaveGState(currentContext);
        CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
        [super drawRect: rect];
        CGContextRestoreGState(currentContext);
    }
    

    【讨论】:

      【解决方案2】:

      一个更简单的方法是在初始化时设置视图的一些层属性:

      self.layer.masksToBounds = NO;
      self.layer.shadowOffset = CGSizeMake(-15, 20);
      self.layer.shadowRadius = 5;
      self.layer.shadowOpacity = 0.5;
      

      你需要导入 QuartzCore。

      #import <QuartzCore/QuartzCore.h>
      

      【讨论】:

      • 但请注意,这仅适用于 iOS 3.2+,因此如果您的应用应在旧版本上运行,您必须使用 Christian 的解决方案或视图后面的静态图像(如果这是一个选项)。
      • 此解决方案还需要将#import &lt;QuartzCore/QuartzCore.h&gt;" 添加到.h 文件中。
      • masksToBounds 设置为NO 将否定cornerRadius,不是吗?
      • 好的,要解决这个问题,需要在图层上设置背景颜色,并且视图需要透明。
      • @pixelfreak 你是怎么做到的?我试过self.layer.backgroundColor = [[UIColor whiteColor] CGColor]; 但没有运气。哪个视图需要透明?
      【解决方案3】:
      self.layer.masksToBounds = NO;
      self.layer.cornerRadius = 8; // if you like rounded corners
      self.layer.shadowOffset = CGSizeMake(-15, 20);
      self.layer.shadowRadius = 5;
      self.layer.shadowOpacity = 0.5;
      

      这会降低应用程序的速度。 只要您的视图是可见的矩形,添加以下行可以提高性能:

      self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
      

      【讨论】:

      • 值得注意的是,这种优化仅在您的视图是可见矩形时才有用。
      • self.layer.shadowPath... 而不是什么?或者只是添加到它
      • 只需在其他行之外添加额外的行。
      • @NathanGaskin - 绘制阴影是一项昂贵的操作,例如,如果您的应用程序允许其他界面方向并且您开始旋转设备,而没有明确指定阴影的路径,则必须多次渲染在那个动画期间,根据形状,可能会明显减慢动画速度
      • @BenjaminDobell 该特定行仅适用于矩形,但您也可以创建非矩形路径。例如,如果你有一个圆角矩形,你可以使用 bezierPathWithRoundedRect:cornerRadius:
      【解决方案4】:

      我将它用作我的实用程序的一部分。有了这个,我们不仅可以设置阴影,还可以为任何UIView 获得圆角。你也可以设置你喜欢的颜色阴影。通常黑色是首选,但有时,当背景不是白色时,您可能需要其他东西。这是我使用的 -

      in utils.m
      + (void)roundedLayer:(CALayer *)viewLayer 
                    radius:(float)r 
                    shadow:(BOOL)s
      {
          [viewLayer setMasksToBounds:YES];
          [viewLayer setCornerRadius:r];        
          [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
          [viewLayer setBorderWidth:1.0f];
          if(s)
          {
              [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
              [viewLayer setShadowOffset:CGSizeMake(0, 0)];
              [viewLayer setShadowOpacity:1];
              [viewLayer setShadowRadius:2.0];
          }
          return;
      }
      

      要使用它,我们需要调用它 - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

      【讨论】:

        【解决方案5】:

        相同的解决方案,但只是提醒您:您可以直接在情节提要中定义阴影。

        例如:

        【讨论】:

        • 可悲的是,我认为 CGColor 从情节提要中遥不可及:(
        • 只需为UIViewCGLayer 定义一个类别,这是UIColor 属性的UIColor 包装器;)
        • 此链接描述了如何执行此操作:cocoadventures.org/post/104137872754/…
        • 为了方便人们复制和粘贴:layer.masksToBound、layer.shadowOffset、layer.shadowRadius、layer.shadowOpacity。错别字会杀了你。
        • layer.shadowOpacity 值应该是 0.5 而不是 0,5
        【解决方案6】:

        对于在此处尝试所有答案后未能使其正常工作的人(作为我自己!),只需确保在属性检查器中未启用 剪辑子视图...

        【讨论】:

          【解决方案7】:

          使用 Interface Builder 的简单而干净的解决方案

          在您的项目中添加一个名为 UIView.swift 的文件(或将其粘贴到任何文件中):

          import UIKit
          
          @IBDesignable extension UIView {
          
              /* The color of the shadow. Defaults to opaque black. Colors created
              * from patterns are currently NOT supported. Animatable. */
              @IBInspectable var shadowColor: UIColor? {
                  set {
                      layer.shadowColor = newValue!.CGColor
                  }
                  get {
                      if let color = layer.shadowColor {
                          return UIColor(CGColor:color)
                      }
                      else {
                          return nil
                      }
                  }
              }
          
              /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
              * [0,1] range will give undefined results. Animatable. */
              @IBInspectable var shadowOpacity: Float {
                  set {
                      layer.shadowOpacity = newValue
                  }
                  get {
                      return layer.shadowOpacity
                  }
              }
          
              /* The shadow offset. Defaults to (0, -3). Animatable. */
              @IBInspectable var shadowOffset: CGPoint {
                  set {
                      layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
                  }
                  get {
                      return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
                  }
              }
          
              /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
              @IBInspectable var shadowRadius: CGFloat {
                  set {
                      layer.shadowRadius = newValue
                  }
                  get {
                      return layer.shadowRadius
                  }
              }
          }
          

          然后,这将在 Interface Builder 中为 Utilities Panel > Attributes Inspector 中的每个视图提供:

          您现在可以轻松设置阴影了。

          注意事项:
          - 阴影不会出现在 IB 中,仅在运行时出现。
          - 正如 Mazen Kasser 所说

          对于那些未能使其正常工作的人 [...] 确保剪辑子视图 (clipsToBounds) 未启用

          【讨论】:

          • 这是正确的答案——未来接口的代码配置
          • 此解决方案导致我出现非工作行为,并带有以下警告消息(每个属性一个):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [&lt;UICollectionViewCell&gt; setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
          • 我必须导入UIKit 才能使其工作,XCode 创建的基本Foundation 导入是不够的,但编译得很好。我应该复制整个源代码。感谢 Axel 提供了这个不错的解决方案。
          【解决方案8】:

          你可以试试这个......你可以玩这些价值观。 shadowRadius 指示模糊量。 shadowOffset 指示阴影的去向。

          斯威夫特 2.0

          let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
          let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
          //Change 2.1 to amount of spread you need and for height replace the code for height
          
          demoView.layer.cornerRadius = 2
          demoView.layer.shadowColor = UIColor.blackColor().CGColor
          demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
          demoView.layer.shadowOpacity = 0.5
          demoView.layer.shadowRadius = 5.0 //Here your control your blur
          demoView.layer.masksToBounds =  false
          demoView.layer.shadowPath = shadowPath.CGPath
          

          斯威夫特 3.0

          let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
          let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
          //Change 2.1 to amount of spread you need and for height replace the code for height
          
          demoView.layer.cornerRadius = 2
          demoView.layer.shadowColor = UIColor.black.cgColor
          demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
          demoView.layer.shadowOpacity = 0.5
          demoView.layer.shadowRadius = 5.0 //Here your control your blur
          demoView.layer.masksToBounds =  false
          demoView.layer.shadowPath = shadowPath.cgPath
          

          传播示例

          创建基本阴影

              demoView.layer.cornerRadius = 2
              demoView.layer.shadowColor = UIColor.blackColor().CGColor
              demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
              demoView.layer.shadowOpacity = 0.5 
              demoView.layer.shadowRadius = 5.0 //Here your control your blur
          

          Swift 2.0 中的基本阴影示例

          【讨论】:

            【解决方案9】:

            斯威夫特 3

            extension UIView {
                func installShadow() {
                    layer.cornerRadius = 2
                    layer.masksToBounds = false
                    layer.shadowColor = UIColor.black.cgColor
                    layer.shadowOffset = CGSize(width: 0, height: 1)
                    layer.shadowOpacity = 0.45
                    layer.shadowPath = UIBezierPath(rect: bounds).cgPath
                    layer.shadowRadius = 1.0
                }
            }
            

            【讨论】:

            • 如果您采用这种方法,我会考虑添加参数,以便您可以调整值使其动态
            • 它没有给我圆角,我做错了吗?
            • @NikhilManapure 什么也没发生,这可能是因为函数 installShadow() 没有从 viewDidLoad()viewWillAppear() 调用
            • 我是从重载的draw 视图方法调用它。阴影正确出现,但圆角没有。
            • @NikhilManapure 没有圆形边框,这可能是因为视图是一个 UIStackView,它只进行布局并且没有视图。您可能需要插入一个常规的UIView 作为所有内容的容器。
            【解决方案10】:

            斯威夫特 3

            self.paddingView.layer.masksToBounds = false
            self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
            self.paddingView.layer.shadowRadius = 5
            self.paddingView.layer.shadowOpacity = 0.5
            

            【讨论】:

              【解决方案11】:

              您可以使用我为阴影和圆角半径创建的实用函数,如下所示:

              - (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{
              
                  // drop shadow
                  [view.layer setShadowRadius:shadowRadius];
                  [view.layer setShadowOpacity:shadowOpacity];
                  [view.layer setShadowOffset:shadowOffset];
                  [view.layer setShadowColor:shadowColor.CGColor];
              
                  // border radius
                  [view.layer setCornerRadius:cornerRadius];
              
                  // border
                  [view.layer setBorderColor:borderColor.CGColor];
                  [view.layer setBorderWidth:borderWidth];
              }
              

              希望对你有帮助!!!

              【讨论】:

                【解决方案12】:

                回答都很好,但我想再补充一点

                如果您在有表格单元格时遇到问题,Deque 一个新单元格存在阴影不匹配,因此在这种情况下,您需要将阴影代码放在 layoutSubviews 方法中,以便它在所有条件下都能正常运行。

                -(void)layoutSubviews{
                    [super layoutSubviews];
                
                    [self.contentView setNeedsLayout];
                    [self.contentView layoutIfNeeded];
                    [VPShadow applyShadowView:self];
                }
                

                或在特定视图的 ViewControllers 中将阴影代码放在以下方法中,以便它正常工作

                -(void)viewDidLayoutSubviews{
                    [super viewDidLayoutSubviews];
                
                    [self.viewShadow layoutIfNeeded];
                    [VPShadow applyShadowView:self.viewShadow];
                }
                

                我已经为新开发者修改了我的影子实现,以获得更通用的形式:

                /*!
                 @brief Add shadow to a view.
                
                 @param layer CALayer of the view.
                
                 */
                +(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
                    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
                    layer.masksToBounds = NO;
                    layer.shadowColor = [UIColor blackColor].CGColor;
                    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
                    layer.shadowOpacity = alpha;
                    layer.shadowRadius = radius;// blur effect
                    layer.shadowPath = shadowPath.CGPath;
                }
                

                【讨论】:

                  【解决方案13】:

                  如果您想使用 StoryBoard 并且不想继续输入运行时属性,您可以轻松地创建视图扩展并使其在 Storyboard 中可用。

                  步骤 1. 创建扩展

                  extension UIView {
                  
                  @IBInspectable var shadowRadius: CGFloat {
                      get {
                          return layer.shadowRadius
                      }
                      set {
                          layer.shadowRadius = newValue
                      }
                  }
                  
                  @IBInspectable var shadowOpacity: Float {
                      get {
                          return layer.shadowOpacity
                      }
                      set {
                          layer.shadowOpacity = newValue
                      }
                  }
                  
                  @IBInspectable var shadowOffset: CGSize {
                      get {
                          return layer.shadowOffset
                      }
                      set {
                          layer.shadowOffset = newValue
                      }
                  }
                  
                  @IBInspectable var maskToBound: Bool {
                      get {
                          return layer.masksToBounds
                      }
                      set {
                          layer.masksToBounds = newValue
                      }
                  }
                  }
                  

                  第 2 步。您现在可以在 storyboard 中使用这些属性

                  【讨论】:

                    【解决方案14】:

                    对于 Xamarin 同胞,答案的 Xamarin.iOS/C# 版本如下所示:

                    public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
                    {
                        CGContext currentContext = UIGraphics.GetCurrentContext();
                        currentContext.SaveState();
                        currentContext.SetShadow(new CGSize(-15, 20), 5);
                        base.DrawRect(area, formatter);
                        currentContext.RestoreState();                
                    }
                    

                    主要区别在于您获取CGContext 的实例,您可以在该实例上直接调用相应的方法。

                    【讨论】:

                      【解决方案15】:

                      你可以使用这个Extension来添加阴影

                      extension UIView {
                      
                          func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
                          {
                              layer.masksToBounds = false
                              layer.shadowOffset = offset
                              layer.shadowColor = color.cgColor
                              layer.shadowRadius = radius
                              layer.shadowOpacity = opacity
                      
                              let backgroundCGColor = backgroundColor?.cgColor
                              backgroundColor = nil
                              layer.backgroundColor =  backgroundCGColor
                          }
                      }
                      

                      你可以这样称呼它

                      your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)
                      

                      【讨论】:

                        【解决方案16】:

                        在 Swift 4 中使用 IBDesignable 和 IBInspectable 绘制阴影

                        如何使用它

                        草图和 XCODE 并排

                        代码

                        @IBDesignable class ShadowView: UIView {
                        
                            @IBInspectable var shadowColor: UIColor? {
                                get {
                                    if let color = layer.shadowColor {
                                        return UIColor(cgColor: color)
                                    }
                                    return nil
                                }
                                set {
                                    if let color = newValue {
                                        layer.shadowColor = color.cgColor
                                    } else {
                                        layer.shadowColor = nil
                                    }
                                }
                            }
                        
                            @IBInspectable var shadowOpacity: Float {
                                get {
                                    return layer.shadowOpacity
                                }
                                set {
                                    layer.shadowOpacity = newValue
                                }
                            }
                        
                            @IBInspectable var shadowOffset: CGPoint {
                                get {
                                    return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
                                }
                                set {
                                    layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
                                }
                        
                             }
                        
                            @IBInspectable var shadowBlur: CGFloat {
                                get {
                                    return layer.shadowRadius
                                }
                                set {
                                    layer.shadowRadius = newValue / 2.0
                                }
                            }
                        
                            @IBInspectable var shadowSpread: CGFloat = 0 {
                                didSet {
                                    if shadowSpread == 0 {
                                        layer.shadowPath = nil
                                    } else {
                                        let dx = -shadowSpread
                                        let rect = bounds.insetBy(dx: dx, dy: dx)
                                        layer.shadowPath = UIBezierPath(rect: rect).cgPath
                                    }
                                }
                            }
                        }
                        

                        输出

                        【讨论】:

                        • 您可以改进从属性中删除阴影前缀的代码。 ShadowView 的目标是明确的阴影操作。还有一件事是您可以为此类添加角半径。 (今天,大多数阴影视图都涉及角半径)
                        猜你喜欢
                        • 2016-11-14
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-02-25
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-08-12
                        • 1970-01-01
                        相关资源
                        最近更新 更多