UIKit应用实现Neumorphic新拟态风格时如何为UIView添加内阴影
实现思路
- UIKit没有原生内阴影属性,核心实现逻辑为外阴影+反向遮罩:创建带外阴影的图层,用遮罩裁剪掉视图边界以外的阴影部分,仅保留视图内部的阴影区域,即可得到内阴影效果。
- 新拟态凹陷效果需要两层内阴影配合:左上角的深色内阴影模拟凹陷背光面,右下角的浅色内阴影模拟凹陷受光面,和你已实现的外阴影逻辑完全对应。
完整实现代码
class DebossedView: UIView { private var outerDarkShadow = CALayer() private var outerLightShadow = CALayer() // 新增内阴影图层 private var innerDarkShadow = CALayer() private var innerLightShadow = CALayer() override init(frame: CGRect) { super.init(frame: frame) setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayers() } override func layoutSubviews() { super.layoutSubviews() // 避免重复添加图层 if outerDarkShadow.superlayer == nil { layer.borderColor = UIColor.white.cgColor layer.borderWidth = 3 layer.cornerRadius = 16 layer.insertSublayer(outerDarkShadow, at: 0) layer.insertSublayer(outerLightShadow, at: 0) layer.addSublayer(innerDarkShadow) layer.addSublayer(innerLightShadow) } // 视图尺寸变化时更新所有图层frame和遮罩 outerDarkShadow.frame = bounds outerLightShadow.frame = bounds innerDarkShadow.frame = bounds innerLightShadow.frame = bounds let maskPath = UIBezierPath(rect: bounds.insetBy(dx: -100, dy: -100)) let cutoutPath = UIBezierPath(roundedRect: bounds, cornerRadius: 16).reversing() maskPath.append(cutoutPath) (innerDarkShadow.mask as? CAShapeLayer)?.path = maskPath.cgPath (innerLightShadow.mask as? CAShapeLayer)?.path = maskPath.cgPath } // 原有外阴影生成方法 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 } // 新增内阴影生成方法 private func innerShadowLayer(color: UIColor, shadowOffset: CGSize, shadowRadius: CGFloat, shadowOpacity: Float) -> CALayer { let layer = CALayer() layer.shadowColor = color.cgColor layer.shadowOffset = shadowOffset layer.shadowRadius = shadowRadius layer.shadowOpacity = shadowOpacity layer.masksToBounds = true layer.cornerRadius = 16 // 反向遮罩:大矩形挖去中间视图区域,仅保留内部阴影 let maskLayer = CAShapeLayer() let maskPath = UIBezierPath(rect: bounds.insetBy(dx: -100, dy: -100)) let cutoutPath = UIBezierPath(roundedRect: bounds, cornerRadius: 16).reversing() maskPath.append(cutoutPath) maskLayer.path = maskPath.cgPath layer.mask = maskLayer return layer } private func setupLayers() { backgroundColor = .gray outerDarkShadow = shadowLayer(color: .black, shadowOffset: 10, shadowRadius: 12) outerLightShadow = shadowLayer(color: .white, shadowOffset: -10, shadowRadius: 10) // 内阴影参数可根据设计需求调整 innerDarkShadow = innerShadowLayer(color: .black, shadowOffset: CGSize(width: 6, height: 6), shadowRadius: 8, shadowOpacity: 0.4) innerLightShadow = innerShadowLayer(color: .white, shadowOffset: CGSize(width: -6, height: -6), shadowRadius: 8, shadowOpacity: 0.4) } }
效果调整说明
- 内阴影的偏移量、半径、透明度参数可根据设计需求微调,透明度建议控制在0.3~0.6区间,避免效果过重。
- 所有圆角参数需保持统一,避免遮罩边缘错位。
- 图层布局逻辑移到了layoutSubviews中,避免draw方法多次调用导致重复添加图层,同时适配视图尺寸变化的场景。
内容的提问来源于stack exchange,提问作者Flavius Dolha
相关产品推荐
相关产品推荐

