11import Foundation
22import SwiftUI
3+ import UIKit
34
45/// Defines shadow styles used across POS UI components.
56/// Design ref: 1qcjzXitBHU7xPnpCOWnNM-fi-22_7198
@@ -8,33 +9,67 @@ enum POSShadowStyle {
89 case large
910}
1011
11- /// A ViewModifier that applies predefined shadow styles.
12+ // MARK: - Shadow Layer Definition
13+
14+ private struct POSShadowLayer {
15+ let color : UIColor
16+ let opacity : Float
17+ let radius : CGFloat
18+ let offset : CGSize
19+ }
20+
21+ private struct POSShadowStyleDefinition {
22+ let shadowLayers : [ POSShadowLayer ]
23+ }
24+
25+ // MARK: - Main SwiftUI Modifier
26+
1227struct POSShadowStyleModifier : ViewModifier {
1328 let style : POSShadowStyle
29+ let cornerRadius : CGFloat
1430
1531 func body( content: Content ) -> some View {
32+ content
33+ . background (
34+ GeometryReader { geometry in
35+ let shadowLayers = shadowLayers ( for: style)
36+ RasterizedShadowBackground (
37+ cornerRadius: cornerRadius,
38+ shadowLayers: shadowLayers,
39+ backgroundColor: UIColor . clear
40+ )
41+ . frame ( width: geometry. size. width, height: geometry. size. height)
42+ }
43+ )
44+ }
45+
46+ private func shadowLayers( for style: POSShadowStyle ) -> [ POSShadowLayer ] {
1647 switch style {
1748 case . medium:
18- content
19- . shadow ( color: Color . posShadow. opacity ( 0.02 ) , radius: 16 , x: 0 , y: 16 )
20- . shadow ( color: Color . posShadow. opacity ( 0.03 ) , radius: 8 , x: 0 , y: 4 )
21- . shadow ( color: Color . posShadow. opacity ( 0.04 ) , radius: 5 , x: 0 , y: 4 )
22- . shadow ( color: Color . posShadow. opacity ( 0.05 ) , radius: 3 , x: 0 , y: 2 )
49+ return [
50+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.02 , radius: 16 , offset: CGSize ( width: 0 , height: 16 ) ) ,
51+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.03 , radius: 8 , offset: CGSize ( width: 0 , height: 4 ) ) ,
52+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.04 , radius: 5 , offset: CGSize ( width: 0 , height: 4 ) ) ,
53+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.05 , radius: 3 , offset: CGSize ( width: 0 , height: 2 ) )
54+ ]
2355 case . large:
24- content
25- . shadow ( color: Color . posShadow. opacity ( 0.02 ) , radius: 43 , x: 0 , y: 50 )
26- . shadow ( color: Color . posShadow. opacity ( 0.04 ) , radius: 36 , x: 0 , y: 30 )
27- . shadow ( color: Color . posShadow. opacity ( 0.07 ) , radius: 27 , x: 0 , y: 15 )
28- . shadow ( color: Color . posShadow. opacity ( 0.08 ) , radius: 15 , x: 0 , y: 5 )
56+ return [
57+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.02 , radius: 43 , offset: CGSize ( width: 0 , height: 50 ) ) ,
58+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.04 , radius: 36 , offset: CGSize ( width: 0 , height: 30 ) ) ,
59+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.07 , radius: 27 , offset: CGSize ( width: 0 , height: 15 ) ) ,
60+ POSShadowLayer ( color: UIColor ( Color . posShadow) , opacity: 0.08 , radius: 15 , offset: CGSize ( width: 0 , height: 5 ) )
61+ ]
2962 }
3063 }
3164}
3265
3366extension View {
3467 /// Applies a shadow style to the view.
35- /// - Parameter style: The shadow style to apply.
36- func posShadow( _ style: POSShadowStyle ) -> some View {
37- modifier ( POSShadowStyleModifier ( style: style) )
68+ /// - Parameters:
69+ /// - style: The shadow style to apply.
70+ /// - cornerRadius: The corner radius for the shadow background. Default is 0.
71+ func posShadow( _ style: POSShadowStyle , cornerRadius: CGFloat = 0 ) -> some View {
72+ modifier ( POSShadowStyleModifier ( style: style, cornerRadius: cornerRadius) )
3873 }
3974}
4075
@@ -45,14 +80,79 @@ extension View {
4580 . frame ( width: 200 , height: 100 )
4681 . foregroundStyle ( Color . posOnSecondaryContainer)
4782 . background ( Color . posOutlineVariant)
48- . posShadow ( . medium)
83+ . posShadow ( . medium, cornerRadius : 12 )
4984
5085 Text ( " Large Shadow " )
5186 . padding ( )
5287 . frame ( width: 200 , height: 100 )
5388 . foregroundStyle ( Color . posOnSecondaryContainer)
5489 . background ( Color . posOutlineVariant)
55- . posShadow ( . large)
90+ . posShadow ( . large, cornerRadius : 16 )
5691 }
5792 . padding ( 100 )
5893}
94+
95+ // MARK: - UIKit Implementation (Private)
96+
97+ /**
98+ UIKit Implementation for POS Shadows
99+ -----------------------------------
100+
101+ Instead of stacking multiple SwiftUI `.shadow` modifiers (which each require separate blur calculations
102+ and compositing operations, which can be costly in lists or scrolling views), this approach uses a
103+ custom `UIView` (`MultiShadowView`) that composes multiple `CALayer` sublayers, each with its own shadow configuration.
104+
105+ Performance Benefits:
106+ - All shadow operations are rasterized and composited using multiple `CALayer`s within a single view hierarchy.
107+ - Avoids SwiftUI’s per-shadow blur and composition cost, which can accumulate in scroll views or complex layouts.
108+ - `CALayer` shadows are GPU-accelerated and benefit from `shouldRasterize`, enabling efficient reuse of cached shadow bitmaps.
109+ - Explicit `shadowPath` avoids expensive runtime shape calculations and improves rendering performance.
110+ */
111+
112+ private class MultiShadowView : UIView {
113+ var cornerRadius : CGFloat = 0
114+ var shadowLayers : [ POSShadowLayer ] = [ ]
115+ var fillColor : UIColor = . clear
116+
117+ override func layoutSubviews( ) {
118+ super. layoutSubviews ( )
119+ layer. sublayers? . removeAll ( where: { $0. name == " posShadowLayer " } )
120+ for (index, shadow) in shadowLayers. enumerated ( ) {
121+ let shadowLayer = CALayer ( )
122+ shadowLayer. name = " posShadowLayer "
123+ shadowLayer. frame = bounds
124+ shadowLayer. backgroundColor = UIColor . clear. cgColor
125+ shadowLayer. cornerRadius = cornerRadius
126+ shadowLayer. shadowColor = shadow. color. cgColor
127+ shadowLayer. shadowOpacity = shadow. opacity
128+ shadowLayer. shadowRadius = shadow. radius
129+ shadowLayer. shadowOffset = shadow. offset
130+ shadowLayer. masksToBounds = false
131+ shadowLayer. shouldRasterize = true
132+ shadowLayer. rasterizationScale = UIScreen . main. scale
133+ shadowLayer. shadowPath = UIBezierPath ( roundedRect: shadowLayer. bounds, cornerRadius: cornerRadius) . cgPath
134+ layer. insertSublayer ( shadowLayer, at: UInt32 ( index) )
135+ }
136+ }
137+ }
138+
139+ private struct RasterizedShadowBackground : UIViewRepresentable {
140+ let cornerRadius : CGFloat
141+ let shadowLayers : [ POSShadowLayer ]
142+ let backgroundColor : UIColor
143+
144+ func makeUIView( context: Context ) -> MultiShadowView {
145+ let view = MultiShadowView ( )
146+ view. cornerRadius = cornerRadius
147+ view. shadowLayers = shadowLayers
148+ view. fillColor = backgroundColor
149+ return view
150+ }
151+
152+ func updateUIView( _ uiView: MultiShadowView , context: Context ) {
153+ uiView. cornerRadius = cornerRadius
154+ uiView. shadowLayers = shadowLayers
155+ uiView. fillColor = backgroundColor
156+ uiView. setNeedsLayout ( )
157+ }
158+ }
0 commit comments