在 UIView 中创建内部阴影以复制 Neumorphic Style

Creating inner shadows in UIView to replicate Neumorphic Style

我的任务是在我的 UIKit 应用程序中实现 neumorphic 设计风格。我已经成功地实现了双外部阴影(黑暗和光明),但不知何故,我无法弄清楚如何在视图内实现内部阴影。我已经尝试过像 CAGradientLayer 这样的方法,从 .black.clear 再到 .white,但它看起来并不像它应该的那样。我在互联网上搜索了各种解决方案,但似乎找不到合适的解决方案。

我应该如何创建内阴影?我应该使用什么方法?我可以在 Swift 或 Objective-C.

中找到解决方案

Current state of the view

The state that I am trying to reach

当前状态的简化版本(外部阴影):

class DebossedView: UIView {

private var outerDarkShadow = CALayer()
private var outerLightShadow = CALayer()

override func draw(_ rect: CGRect) {
    outerDarkShadow = shadowLayer(color: UIColor.black, shadowOffset: 10, shadowRadius: 12)
    outerLightShadow = shadowLayer(color: UIColor.white, shadowOffset: -10, shadowRadius: 10)
    layer.borderColor = UIColor.white.cgColor
    layer.borderWidth = 3
    layer.insertSublayer(outerDarkShadow, at: 0)
    layer.insertSublayer(outerLightShadow, at: 0)
}

private func shadowLayer(color: UIColor, shadowOffset: CGFloat, shadowRadius: CGFloat) -> CALayer {
    let shadowLayer = CALayer()
    shadowLayer.frame = bounds
    shadowLayer.backgroundColor = UIColor.gray.cgColor
    shadowLayer.shadowColor = color.cgColor
    shadowLayer.cornerRadius = 16
    shadowLayer.shadowOffset = CGSize(width: shadowOffset, height: shadowOffset)
    shadowLayer.shadowOpacity = 1
    shadowLayer.shadowRadius = shadowRadius
    return shadowLayer
}
}

Neumorphism information

如果我没记错的话,您必须创建一个包含模糊形状的叠加层,并剪裁到该形状。 剪裁后,模糊的外部部分将消失,您只会看到内部模糊作为阴影。

您的“目标”图片与您为“Neumorphism informations”发布的 link 中的示例不匹配,因此不确定这是否会给您想要的确切结果。

然而,一些注意事项...

  1. draw() 绝对不是 您要创建和 adding/inserting 子层的地方。
  2. 您可以使用 CAGradientLayer 获得“内部”外观(基于您的目标图像)
  3. 您可以添加一个 UIImageView 作为子视图来保存图像。

所以,这是一个例子 UIView subclass:

class NeuView: UIView {
    
    public var image: UIImage? {
        didSet {
            imgView.image = image
        }
    }
    
    private let imgView = UIImageView()
    private let darkShadow = CALayer()
    private let lightShadow = CALayer()
    
    private let gradientLayer = CAGradientLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() -> Void {

        // add sublayers
        self.layer.addSublayer(darkShadow)
        self.layer.addSublayer(lightShadow)
        self.layer.addSublayer(gradientLayer)

        darkShadow.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
        darkShadow.shadowOffset = CGSize(width: 5, height: 5)
        darkShadow.shadowOpacity = 1
        darkShadow.shadowRadius = 10

        lightShadow.shadowColor = UIColor.white.withAlphaComponent(0.9).cgColor
        lightShadow.shadowOffset = CGSize(width: -5, height: -5)
        lightShadow.shadowOpacity = 1
        lightShadow.shadowRadius = 10

        // 45-degree gradient layer
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 1)
        
        
        self.layer.borderColor = UIColor.white.withAlphaComponent(0.5).cgColor
        layer.borderWidth = 3
        
        // very light gray background color
        let bkgColor = UIColor(white: 0.95, alpha: 1.0)
        
        darkShadow.backgroundColor = bkgColor.cgColor
        lightShadow.backgroundColor = bkgColor.cgColor
        
        // set gradient colors from
        //  slightly darker than background to
        //  slightly lighter than background
        let c1 = UIColor(white: 0.92, alpha: 1.0)
        let c2 = UIColor(white: 0.97, alpha: 1.0)
        gradientLayer.colors = [c1.cgColor, c2.cgColor]

        // image view properties
        imgView.contentMode = .scaleAspectFit
        imgView.translatesAutoresizingMaskIntoConstraints = false
        //imgView.layer.masksToBounds = true
        
        addSubview(imgView)
        
        NSLayoutConstraint.activate([
            
            // let's make the image view 60% of self
            imgView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
            imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
            imgView.centerXAnchor.constraint(equalTo: centerXAnchor),
            imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
            
        ])
        
    }
    
    override func layoutSubviews() {
        
        super.layoutSubviews()
        
        // set all layers' frames to bounds
        darkShadow.frame = bounds
        lightShadow.frame = bounds
        gradientLayer.frame = bounds
        
        // set all layers' cornerRadius to one-half height
        let cr = bounds.height * 0.5
        darkShadow.cornerRadius = cr
        lightShadow.cornerRadius = cr
        gradientLayer.cornerRadius = cr
        layer.cornerRadius = cr
        
    }
    
}

和一个示例视图控制器:

class NeuTestVC: UIViewController {
    
    let neuView = NeuView()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        guard let img = UIImage(named: "neu01") else {
            print("Could not load image!")
            return
        }
        
        neuView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(neuView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            neuView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
            neuView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
            neuView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            neuView.heightAnchor.constraint(equalTo: neuView.widthAnchor),
            
        ])

        // set the image
        neuView.image = img
        
    }
    
}

使用此图片(透明背景):

这是结果:

通过调整颜色、阴影属性等,这可能会给您想要的结果。


编辑

这里是 NewView class.

的修改版本

我们不使用渐变层,而是添加一个带有“孔”切口的 CAShapeLayer,并使用该层来投射“内阴影”:

class NeuView: UIView {
    
    public var image: UIImage? {
        didSet {
            imgView.image = image
        }
    }
    
    private let imgView = UIImageView()
    
    // "outer" shadows
    private let darkShadow = CALayer()
    private let lightShadow = CALayer()
    
    // "inner" shadow
    private let innerShadowLayer = CAShapeLayer()
    private let innerShadowMaskLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() -> Void {
        
        // add sublayers
        self.layer.addSublayer(darkShadow)
        self.layer.addSublayer(lightShadow)
        self.layer.addSublayer(innerShadowLayer)
        
        darkShadow.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
        darkShadow.shadowOffset = CGSize(width: 5, height: 5)
        darkShadow.shadowOpacity = 1
        darkShadow.shadowRadius = 10
        
        lightShadow.shadowColor = UIColor.white.withAlphaComponent(0.9).cgColor
        lightShadow.shadowOffset = CGSize(width: -5, height: -5)
        lightShadow.shadowOpacity = 1
        lightShadow.shadowRadius = 10
        
        self.layer.borderColor = UIColor.white.withAlphaComponent(0.5).cgColor
        //layer.borderWidth = 3
        
        // very light gray background color
        let bkgColor = UIColor(red: 0.94, green: 0.95, blue: 0.99, alpha: 1.0) // UIColor(white: 0.95, alpha: 1.0)
        
        darkShadow.backgroundColor = bkgColor.cgColor
        lightShadow.backgroundColor = bkgColor.cgColor // UIColor(white: 0.98, alpha: 1.0).cgColor
        
        // image view properties
        imgView.contentMode = .scaleAspectFit
        imgView.translatesAutoresizingMaskIntoConstraints = false
        //imgView.layer.masksToBounds = true
        
        addSubview(imgView)
        
        NSLayoutConstraint.activate([
            
            // let's make the image view 60% of self
            imgView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
            imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
            imgView.centerXAnchor.constraint(equalTo: centerXAnchor),
            imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
            
        ])
        
    }
    
    override func layoutSubviews() {
        
        super.layoutSubviews()
        
        // set dark and light shadow layers' frames to bounds
        darkShadow.frame = bounds
        lightShadow.frame = bounds
        
        // set self.layer and dark and light shadow layers' cornerRadius to one-half height
        let cr = bounds.height * 0.5
        darkShadow.cornerRadius = cr
        lightShadow.cornerRadius = cr
        self.layer.cornerRadius = cr
        
        // for the "inner" shadow,
        // rectangle path needs to be larger than
        //  bounds + shadow offset + shadow raidus
        // so the shadow doesn't "bleed" from all sides
        let path = UIBezierPath(rect: bounds.insetBy(dx: -40, dy: -40))

        // create a path for the "hole" in the layer
        let circularHolePath = UIBezierPath(ovalIn: bounds)
        
        // this "cuts a hole" in the path
        path.append(circularHolePath)
        path.usesEvenOddFillRule = true
        
        innerShadowLayer.path = path.cgPath
        innerShadowLayer.fillRule = .evenOdd
        
        // fillColor doesn't matter - just needs to be opaque
        innerShadowLayer.fillColor = UIColor.white.cgColor

        // mask the layer, so we only "see through the hole"
        innerShadowMaskLayer.path = circularHolePath.cgPath
        innerShadowLayer.mask = innerShadowMaskLayer

        // adjust properties as desired
        innerShadowLayer.shadowOffset = CGSize(width: 15, height: 15)
        innerShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
        innerShadowLayer.shadowRadius = 5
        
        // setting .shadowOpacity to a very small value (such as 0.025)
        //  results in very light shadow
        // set .shadowOpacity to 1.0 to clearly see
        //  what the shadow is doing
        innerShadowLayer.shadowOpacity = 0.025

    }

}

示例视图控制器:

class NeuTestVC: UIViewController {
    
    let neuView = NeuView()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(red: 0.94, green: 0.95, blue: 0.99, alpha: 1.0)
        
        guard let img = UIImage(named: "neu01") else {
            print("Could not load image!")
            return
        }
        
        neuView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(neuView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            neuView.topAnchor.constraint(equalTo: g.topAnchor, constant: 60.0),
            neuView.widthAnchor.constraint(equalToConstant: 125.0),
            neuView.heightAnchor.constraint(equalTo: neuView.widthAnchor),
            neuView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
        ])
        
        // set the image
        neuView.image = img

    }
    
}

结果 - 顶部实例的“内阴影”不透明度设置为 0.9(以使其清晰可见)...底部实例设置为 0.025: