44
55import SwiftUI
66
7+ public enum SplashScreenMode {
8+ case carousel
9+ case `static`
10+ }
11+
12+ public struct SplashFeature : Identifiable {
13+ public let id = UUID ( )
14+ public let title : String
15+ public let icon : String
16+
17+ public init ( title: String , icon: String ) {
18+ self . title = title
19+ self . icon = icon
20+ }
21+ }
22+
723@available ( iOS 18 . 0 , * )
824public struct SplashScreen : View {
925 @State var prVisible : Bool = false
@@ -12,40 +28,177 @@ public struct SplashScreen: View {
1228 @State var timer : Timer ?
1329 @State var photos : [ Photo ]
1430
31+ var mode : SplashScreenMode
1532 var title : String
1633 var product : String
1734 var caption : String
35+ var features : [ SplashFeature ]
36+ var footerText : String ?
1837 var ctaText : String
1938 var ctaAction : ( ) -> Void
39+ var secondaryCtaText : String ?
40+ var secondaryCtaAction : ( ( ) -> Void ) ?
2041
21- public init ( images: [ Photo ] , title: String , product: String , caption: String , cta: String , action: @escaping ( ) -> Void ) {
42+ public init (
43+ mode: SplashScreenMode = . carousel,
44+ images: [ Photo ] ,
45+ title: String ,
46+ product: String ,
47+ caption: String ,
48+ features: [ SplashFeature ] = [ ] ,
49+ footer: String ? = nil ,
50+ cta: String ,
51+ secondaryCta: String ? = nil ,
52+ secondaryAction: ( ( ) -> Void ) ? = nil ,
53+ action: @escaping ( ) -> Void
54+ ) {
55+ self . mode = mode
2256 self . _photos = State ( initialValue: images) // Initialize @State variable
2357 self . title = title
2458 self . product = product
2559 self . caption = caption
60+ self . features = features
61+ self . footerText = footer
2662 self . ctaText = cta
2763 self . ctaAction = action
64+ self . secondaryCtaText = secondaryCta
65+ self . secondaryCtaAction = secondaryAction
2866 }
2967
3068 public var body : some View {
3169 ZStack {
32- Image ( " \( photos [ currentIndex] . title) " )
33- . resizable ( )
34- . ignoresSafeArea ( . all)
35- . blur ( radius: 10 )
36- VStack {
37- pagingRotation
38- . offset ( y: prVisible ? 0 : - 500 )
39- . transition ( . move( edge: . top) )
40- . animation ( . easeInOut( duration: 1 ) )
41- cta
42- Spacer ( )
70+ if mode == . carousel {
71+ SplashScreenImage ( photo: photos [ currentIndex] )
72+ . ignoresSafeArea ( . all)
73+ . blur ( radius: 10 )
74+ VStack {
75+ pagingRotation
76+ . offset ( y: prVisible ? 0 : - 500 )
77+ . transition ( . move( edge: . top) )
78+ . animation ( . easeInOut( duration: 1 ) )
79+ cta
80+ Spacer ( )
81+ }
82+ . background ( . black. opacity ( 0.8 ) )
83+ . background ( . ultraThinMaterial)
84+ } else {
85+ staticLayout
4386 }
44- . background ( . black. opacity ( 0.8 ) )
45- . background ( . ultraThinMaterial)
4687 }
4788 }
4889
90+ public var staticLayout : some View {
91+ VStack ( spacing: 0 ) {
92+ ScrollView {
93+ VStack ( spacing: 0 ) {
94+ // Header Image
95+ if let firstPhoto = photos. first {
96+ Rectangle ( )
97+ . fill ( . clear)
98+ . frame ( height: 400 )
99+ . overlay {
100+ SplashScreenImage ( photo: firstPhoto)
101+ }
102+ . clipped ( )
103+ . overlay {
104+ LinearGradient (
105+ stops: [
106+ . init( color: . clear, location: 0.8 ) ,
107+ . init( color: . black, location: 1.0 )
108+ ] ,
109+ startPoint: . top,
110+ endPoint: . bottom
111+ )
112+ }
113+ }
114+
115+ // Content
116+ VStack ( spacing: 12 ) {
117+ HStack ( spacing: 8 ) {
118+ Image ( systemName: " apple.logo " )
119+ . font ( . title3)
120+ Text ( title)
121+ . font ( . title3)
122+ . fontWeight ( . semibold)
123+ }
124+ . foregroundStyle ( . white)
125+
126+ Text ( product)
127+ . font ( . system( size: 24 , weight: . bold) )
128+ . multilineTextAlignment ( . center)
129+ . foregroundStyle ( . white)
130+ . padding ( . horizontal)
131+ . minimumScaleFactor ( 0.5 )
132+ . lineLimit ( 2 )
133+ . fixedSize ( horizontal: false , vertical: true )
134+
135+ Text ( caption)
136+ . font ( . subheadline)
137+ . multilineTextAlignment ( . center)
138+ . foregroundStyle ( . white. opacity ( 0.8 ) )
139+ . padding ( . horizontal, 40 )
140+ . lineLimit ( 2 )
141+ . minimumScaleFactor ( 0.8 )
142+ . fixedSize ( horizontal: false , vertical: true )
143+
144+ if !features. isEmpty {
145+ VStack ( alignment: . leading, spacing: 12 ) {
146+ ForEach ( features) { feature in
147+ HStack ( spacing: 15 ) {
148+ Image ( systemName: feature. icon)
149+ . foregroundStyle ( . white)
150+ . frame ( width: 24 )
151+ Text ( feature. title)
152+ . font ( . footnote)
153+ . foregroundStyle ( . white)
154+ }
155+ }
156+ }
157+ . padding ( . top, 10 )
158+ }
159+ }
160+ . padding ( . top, 20 )
161+ }
162+ }
163+ . scrollIndicators ( . hidden)
164+
165+ // Fixed Bottom Area
166+ VStack ( spacing: 12 ) {
167+ if let footerText = footerText {
168+ Text ( footerText)
169+ . font ( . caption2)
170+ . foregroundStyle ( . blue)
171+ . padding ( . horizontal)
172+ }
173+
174+ Button ( action: ctaAction) {
175+ Text ( ctaText)
176+ . fontWeight ( . semibold)
177+ . frame ( maxWidth: . infinity)
178+ . padding ( . vertical, 12 )
179+ }
180+ . buttonStyle ( . borderedProminent)
181+ . buttonBorderShape ( . capsule)
182+ . tint ( . blue)
183+ . padding ( . horizontal, 40 )
184+
185+ if let secondaryCtaText = secondaryCtaText {
186+ Button ( action: { secondaryCtaAction ? ( ) } ) {
187+ Text ( secondaryCtaText)
188+ . font ( . subheadline)
189+ . foregroundStyle ( . blue)
190+ }
191+ . padding ( . bottom, 10 )
192+ }
193+ }
194+ . padding ( . top, 20 )
195+ . padding ( . bottom, 10 )
196+ . background ( . black)
197+ }
198+ . background ( . black)
199+ . ignoresSafeArea ( edges: . top)
200+ }
201+
49202 public var pagingRotation : some View {
50203 GeometryReader { geometry in
51204 ScrollView ( . horizontal, showsIndicators: false ) {
@@ -139,3 +292,37 @@ public struct SplashScreen: View {
139292 . animation ( . easeInOut( duration: 2 ) )
140293 }
141294}
295+
296+ struct SplashScreenImage : View {
297+ var photo : Photo
298+
299+ var body : some View {
300+ Group {
301+ if let url = URL ( string: photo. title) , url. scheme != nil {
302+ AsyncImage ( url: url) { phase in
303+ switch phase {
304+ case . success( let image) :
305+ image
306+ . resizable ( )
307+ . aspectRatio ( contentMode: . fill)
308+ case . failure( _) :
309+ Color . gray
310+ . overlay {
311+ Image ( systemName: " photo " )
312+ . foregroundStyle ( . white)
313+ }
314+ case . empty:
315+ ProgressView ( )
316+ . tint ( . white)
317+ @unknown default :
318+ EmptyView ( )
319+ }
320+ }
321+ } else {
322+ Image ( photo. title)
323+ . resizable ( )
324+ . aspectRatio ( contentMode: . fill)
325+ }
326+ }
327+ }
328+ }
0 commit comments