Skip to content

Commit e565156

Browse files
committed
Add static mode and feature content to SplashScreen
Introduce a static display mode and richer content for SplashScreen: add SplashScreenMode enum and SplashFeature struct; extend SplashScreen with mode, features, footer and secondary CTA properties; update initializer to accept defaults and mode. Implement staticLayout view (header image, caption, feature list, footer, primary/secondary CTAs) and SplashScreenImage helper that loads remote or local images via AsyncImage. Add a Demo preview showcasing the new static Creator Studio splash. Also update project settings in project.pbxproj to set SUPPORTED_PLATFORMS and disable Mac Catalyst/Mac/XR flags; user workspace state file changed.
1 parent f77f876 commit e565156

4 files changed

Lines changed: 235 additions & 14 deletions

File tree

Demo/Demo.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@
429429
MARKETING_VERSION = 1.1;
430430
PRODUCT_BUNDLE_IDENTIFIER = SplashScreen.Demo;
431431
PRODUCT_NAME = "$(TARGET_NAME)";
432+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
433+
SUPPORTS_MACCATALYST = NO;
434+
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
435+
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
432436
SWIFT_EMIT_LOC_STRINGS = YES;
433437
SWIFT_VERSION = 5.0;
434438
TARGETED_DEVICE_FAMILY = "1,2";
@@ -461,6 +465,10 @@
461465
MARKETING_VERSION = 1.1;
462466
PRODUCT_BUNDLE_IDENTIFIER = SplashScreen.Demo;
463467
PRODUCT_NAME = "$(TARGET_NAME)";
468+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
469+
SUPPORTS_MACCATALYST = NO;
470+
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
471+
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
464472
SWIFT_EMIT_LOC_STRINGS = YES;
465473
SWIFT_VERSION = 5.0;
466474
TARGETED_DEVICE_FAMILY = "1,2";

Demo/Demo/ContentView.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,29 @@ struct ContentView: View {
9595
print("Hello, Journal!")
9696
}
9797
}
98+
99+
#Preview("Creator Studio") {
100+
SplashScreen(
101+
mode: .static,
102+
images: [
103+
Photo("https://www.apple.com/newsroom/images/2026/01/introducing-apple-creator-studio-an-inspiring-collection-of-creative-apps/article/Apple-Creator-Studio-lifestyle-Pixelmator-Pro_big.jpg.large_2x.jpg")
104+
],
105+
title: "Creator Studio",
106+
product: "Your new device includes 3 months of Creator Studio for free.",
107+
caption: "Bring your vision to life with powerful apps for video and music, creative imaging, and much more.",
108+
features: [
109+
SplashFeature(title: "Create stunning videos with Final Cut Pro", icon: "video"),
110+
SplashFeature(title: "Record, mix, and master music with Logic Pro", icon: "waveform"),
111+
SplashFeature(title: "Retouch images, design, and draw with Pixelmator Pro", icon: "paintbrush"),
112+
SplashFeature(title: "Premium features in Pages, Numbers, and Keynote", icon: "star.fill")
113+
],
114+
footer: "3 months free, then $12.99/month. Terms apply.",
115+
cta: "Accept 3 Months Free",
116+
secondaryCta: "See All Plans",
117+
secondaryAction: {
118+
print("See All Plans")
119+
}
120+
) {
121+
print("Accept 3 Months Free")
122+
}
123+
}

Sources/SplashScreenKit/SplashScreenKit.swift

Lines changed: 201 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@
44

55
import 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, *)
824
public 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

Comments
 (0)