UIView阴影和InterfaceBuilder

17

我想使用CALayer为UI(Image)View添加一个阴影。以下代码通常可以正常工作。

previewImage.layer.shadowColor = [[UIColor blackColor] CGColor];
previewImage.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
previewImage.layer.shadowOpacity = 1.0f;
previewImage.layer.shadowRadius = 8.0f;

然而,这仅在我通过编程方式创建该视图并将其作为子视图添加到我的主视图中时才起作用。当该视图在InterfaceBuilder中设置并定义为IBOutlet UIImageView时,这种方法不起作用。没有阴影出现。

所以我错过了什么?


1
非常随机地,我刚刚发现实际问题似乎不是InterfaceBuilder,而是我将InterfaceBuilder视图的clipsToBounds设置为YES。这里设置为NO就可以了。所以我想我必须用clipsToBound = NO和阴影将视图包装在第二个视图中。还有其他方法吗? - Dennis
我曾经遇到过同样的问题,但是这是因为在Interface Builder中对视图勾选了“剪裁子视图”。取消勾选后,CALayer阴影就可以显示出来了。 - dvs
5个回答

26

在你的项目中添加一个名为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

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

注:
- 阴影只会在运行时显示。
- `clipsToBounds` 应该是 false(默认情况下是这样)


shadowColor 会与 UILabel 冲突。请参考 https://developer.apple.com/documentation/uikit/uilabel/1620536-shadowcolor。 - Zaporozhchenko Oleksandr

17

我知道这个问题很久了,但最近我遇到了一个类似的情况,所以我决定为那些处于这种情况的人提供我的答案。

我想在界面构建器中设置UIViewborderColorshadowColor,但是图层的borderColor属性的类型是CGColor(就像shadowColor一样),不能在用户定义的运行时属性特性中更改此类型之一。因此,我为CALayer制作了一个扩展,并添加了两个属性,称为borderColorIB和shadowColorIB,它们的类型是UIColor:

RuntimeAttributes.h

@import QuartzCore;

@interface CALayer (IBConfiguration)

@property(nonatomic, assign) UIColor* borderColorIB;
@property(nonatomic, assign) UIColor* shadowColorIB;

@end

RuntimeAttributes.m

#import <UIKit/UIKit.h>
#import "RuntimeAttributes.h"

@implementation CALayer (IBConfiguration)

-(void)setBorderColorIB:(UIColor*)color
{
    self.borderColor = color.CGColor;
}

-(UIColor*)borderColorIB
{
    return [UIColor colorWithCGColor:self.borderColor];
}

-(void)setShadowColorIB:(UIColor*)color
{
    self.shadowColor = color.CGColor;
}

-(UIColor*)shadowColorIB
{
    return [UIColor colorWithCGColor:self.shadowColor];
}

@end

现在我已经可以通过Interface Builder设置这两个属性,方法如下:

  1. 在 'user-defined runtime attributes' 部分(Identity inspector)
  2. 确保选中UIView,并添加以下运行时属性:

    • layer.borderWidth, Number, 1
    • layer.borderColorIB, Color, someColor <- 这是我自定义的属性来设置边框颜色
    • layer.shadowColorIB, Color, someColor <- 这是我自定义的属性来设置阴影颜色
    • layer.shadowOpacity, Number, 0.8
    • layer.shadowOffset, size, {5,5}
    • layer.cornerRadius, Number, 5

以下图片说明了具体操作:

enter image description here

... 然后结果将在运行时而不是Xcode中显示:

enter image description here

希望对某些人有所帮助!


3
我不确定问题出在哪里-请确保您的UIImageViewclipsToBounds属性设置为NO。您可以在从nib文件加载后在viewDidLoad中通过引用您的IBOutlet来完成此操作。您不需要将其包装在另一个视图中。

编辑

考虑到您需要使用纵横比填充缩放图像,您可以使用UIImageView的底层图层的contentsRect属性来“模拟”内容剪辑的效果。contentsRect是该层内容(在本例中为您的图像)中单位坐标空间中的矩形,定义应绘制的内容的子矩形。

通过一点数学计算,我们可以通过比较图像视图大小和图像大小(考虑到纵横比填充缩放)找到这个矩形:

CGSize imageViewSize = previewImage.size;
CGSize imageSize = previewImage.image.size;

// Find the scaling required for the image to fit the image view (as for aspect fill).
CGFloat imageWidthScale = fabsf(imageViewSize.width / imageSize.width);
CGFloat imageHeightScale = fabsf(imageViewSize.height / imageSize.height);
CGFloat imageScale = (imageWidthScale > imageHeightScale) ? imageWidthScale : imageHeightScale;

// Determine the new image size, after scaling.
CGSize scaledImageSize = CGSizeApplyAffineTransform(imageSize, CGAffineTransformMakeScale(imageScale, imageScale));

// Set the layer's contentsRect property in order to 'clip' the image to the image view's bounds.
previewImage.layer.contentsRect = CGRectMake(((scaledImageSize.width - imageViewSize.width) / 2.0f) / scaledImageSize.width,
                                             ((scaledImageSize.height - imageViewSize.height) / 2.0f) / scaledImageSize.height,
                                             imageViewSize.width / scaledImageSize.width,
                                             imageViewSize.height / scaledImageSize.height);

通过这样做,您可以将clipsToBounds设置为NO,但图像仍会被裁剪。如果您需要调整图像视图的大小,将此代码封装成一个以UIImageView作为参数的方法可能会很方便。

希望这能帮到您。


我同意。不需要包装,只是不要剪切到边界 - 因为阴影在边界之外。 - bandejapaisa
我需要clipToBounds的原因是,图像太大无法适应视图,我需要使用“Aspect Fill”来让它看起来好看。当然,我可以通过完全绘制新图像并通过UIGraphicsGetImageFromCurrentImageContext()获取合适的图像来裁剪图像本身。但那不是很浪费吗? - Dennis
@Dennis: 如果图像是静态的并且将始终以该大小使用,则我的第一建议是首先在图像编辑软件中重新创建该图像,然后在项目的图像视图中使用它,无需更改其几何形状。 但如果这不是选项,则请参见我上面的编辑。 - Stuart

3

这是mauricioconde回答的Swift 5 Xcode 11.3.1版本。请注意,如果没有使用@IBInspectable,它将无法工作。

import UIKit
import QuartzCore

extension CALayer {

    @IBInspectable
    var shadowUIColor: UIColor? {
        get { shadowColor != nil ? UIColor(cgColor: shadowColor!) : nil }
        set { shadowColor = newValue?.cgColor }
    }
}

enter image description here


1

使用 Interface Builder 和 Swift4 为 UIView 添加带有圆角的阴影

    extension UIView {

        @IBInspectable
        var cornerRadius: CGFloat {
            get {
                return layer.cornerRadius
            }
            set {
                layer.cornerRadius = newValue
                if shadowOpacity > 0.0 {
                    layer.masksToBounds = false
                }
                else {
                    layer.masksToBounds = true
                }
            }
        }
      @IBInspectable
        var borderWidth: CGFloat {
            get {
                return layer.borderWidth
            }
            set {
                layer.borderWidth = newValue
            }
        }
    @IBInspectable
        var borderColor: UIColor? {
            get {
                if let color = layer.borderColor {
                    return UIColor(cgColor: color)
                }
                return nil
            }
            set {
                if let color = newValue {
                    layer.borderColor = color.cgColor
                } else {
                    layer.borderColor = nil
                }
            }
      @IBInspectable var shadowColor: UIColor? {
            set {
                layer.shadowColor = newValue!.cgColor
            }
            get {
                if let color = layer.shadowColor {
                    return UIColor(cgColor: color)
                }
                else {
                    return nil
                }
            }
        }
       @IBInspectable var shadowOpacity: Float {
            set {
                layer.shadowOpacity = newValue
            }
            get {
                return layer.shadowOpacity
            }
        }
     @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)
            }
        }
     @IBInspectable var shadowRadius: CGFloat {
            set {
                layer.shadowRadius = newValue
            }
            get {
                return layer.shadowRadius
            }
        }
}

shadowColor 会与 UILabel 冲突。请参考 https://developer.apple.com/documentation/uikit/uilabel/1620536-shadowcolor。 - Zaporozhchenko Oleksandr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接