SwiftUI में एनिमेटेड स्प्लैश स्क्रीन (कोड पैटर्न)
Charmotion टीम द्वारा · अपडेट:

iOS लॉन्च स्क्रीन को खुद एनिमेट नहीं किया जा सकता, लेकिन आप अपने पहले SwiftUI view को लॉन्च स्क्रीन जैसे ही रूप से शुरू करके वहाँ एक छोटा इंट्रो चला सकते हैं। ZStack में ContentView के ऊपर एक इंट्रो view रखें, एनिमेशन खत्म होने पर उसे fade के साथ हटाएँ, और Reduce Motion चालू होने पर उसे स्किप करें।
मुख्य बातें
- लॉन्च स्क्रीन एक स्थिर, सिस्टम द्वारा रेंडर की गई configuration है; इसमें कोई code नहीं चलता।
- इंट्रो का पहला फ़्रेम लॉन्च स्क्रीन से मिलाएँ (वही बैकग्राउंड रंग, इमेज की वही पोज़िशन), ताकि कोई दिखने वाला झटका न आए।
- इंट्रो के नीचे
ContentViewलोड करें, ताकि इंट्रो खत्म होते ही ऐप तैयार हो। - इंट्रो को एनिमेशन के सिग्नल (ट्रिगर स्ट्रीम) पर खत्म करें और साथ में timeout fallback रखें, सिर्फ़ अंदाज़े वाले timer पर निर्भर न रहें।
- Reduce Motion चालू होने पर और warm launch पर इंट्रो स्किप करें।
क्या iOS की लॉन्च स्क्रीन खुद एनिमेट हो सकती है?
नहीं। लॉन्च स्क्रीन आपका कोई भी code चलने से पहले सिस्टम बनाता है, या तो Info.plist की UILaunchScreen dictionary से या launch storyboard से। इनमें से कोई भी custom class या एनिमेशन नहीं चला सकता।
Apple की Human Interface Guidelines यह भी सीमित करती हैं कि इसमें क्या दिखना चाहिए: "ऐसी लॉन्च स्क्रीन डिज़ाइन करें जो आपके ऐप की पहली स्क्रीन से लगभग एक जैसी हो", और "विज्ञापन न करें। लॉन्च स्क्रीन ब्रांडिंग का मौका नहीं है।" HIG आगे कहती है कि अगर आपको स्प्लैश स्क्रीन चाहिए, तो "उसे अपने ऑनबोर्डिंग फ़्लो की शुरुआत में दिखाने पर विचार करें।" macOS, visionOS और watchOS ऐप को लॉन्च स्क्रीन की ज़रूरत ही नहीं होती।
इसलिए iOS पर "एनिमेटेड स्प्लैश स्क्रीन" असल में एक के बाद एक आने वाली दो चीज़ें हैं:
| लॉन्च स्क्रीन | इंट्रो (आपकी स्प्लैश) | |
|---|---|---|
| रेंडर कौन करता है | सिस्टम | आपका पहला SwiftUI view |
| कंटेंट | स्थिर रंग और इमेज | कुछ भी, Rive या Lottie भी |
| कहाँ कॉन्फ़िगर होता है | Info.plist UILaunchScreen या storyboard | कोड |
| अवधि | जब तक आपका पहला फ़्रेम तैयार न हो | आप तय करते हैं |

स्थिर लॉन्च स्क्रीन से एनिमेटेड पहले view पर कैसे जाएँ?
root view को ZStack बनाएँ: नीचे आपका असली ContentView, और जब तक flag सेट है, ऊपर इंट्रो। ContentView तुरंत लोड होना शुरू हो जाता है, इसलिए इंट्रो लोडिंग का समय बढ़ाता नहीं, बल्कि उसे छिपा देता है।
import SwiftUI
@main
struct PipApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}
struct RootView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var showIntro = true
var body: some View {
ZStack {
ContentView()
if showIntro && !reduceMotion {
IntroView {
withAnimation(.easeOut(duration: 0.25)) { showIntro = false }
}
.transition(.opacity)
.zIndex(1)
}
}
}
}
.zIndex(1) हटाने वाले transition के दौरान fade होते इंट्रो को ContentView के ऊपर रखता है। इसके बिना बाहर जाता view नीचे बन सकता है और fade दिखाई नहीं देता।
वन-शॉट Rive इंट्रो चलाकर फिर ContentView कैसे दिखाएँ?
इंट्रो, Rive के नए Apple रनटाइम से एक launch फ़ाइल लोड करता है, उसे चलाता है, और एनिमेशन के खत्म होने का सिग्नल मिलने पर onFinish कॉल करता है। नए रनटाइम में state-change callback नहीं है, इसलिए सबसे साफ़ सिग्नल एक ट्रिगर प्रॉपर्टी है जिसे फ़ाइल इंट्रो के अंत में फ़ायर करती है; इसे stream(of:) से observe करें। एक timeout पक्का करता है कि ऐप कभी अटके नहीं।
import SwiftUI
import RiveRuntime
struct IntroView: View {
let onFinish: () -> Void
@State private var rive: Rive?
@State private var instance: ViewModelInstance?
var body: some View {
ZStack {
Color("LaunchBackground").ignoresSafeArea()
RiveUIViewRepresentable(rive: rive)
.frame(width: 200, height: 200)
.accessibilityHidden(true)
}
.task { await play() }
}
private func play() async {
let timeout = Task { @MainActor in
try? await Task.sleep(for: .seconds(3))
onFinish()
}
do {
let worker = try await Worker()
let file = try await File(source: .local("launch", Bundle.main), worker: worker)
let artboard = try await file.createArtboard()
let instance = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let machine = try await artboard.createStateMachine("LaunchStateMachine", binding: instance)
self.instance = instance
rive = try await Rive(file: file, artboard: artboard, stateMachine: machine)
// A Charmotion LaunchStateMachine plays its 2.6 s intro once and settles on idle;
// it fires no "done" trigger, so wait for the intro's length. If your own file
// fires an end trigger, await it instead: instance.stream(of: TriggerProperty(path: …)).
try await Task.sleep(for: .seconds(2.6))
} catch {
// Load failure or cancellation: fall through and show the app.
}
timeout.cancel()
onFinish()
}
}
यहाँ onFinish को दो बार कॉल करने से कोई नुकसान नहीं, क्योंकि यह सिर्फ़ showIntro को false सेट करता है। अगर आपकी launch फ़ाइल में कोई व्यू मॉडल नहीं है, तो createViewModelInstance error throw करता है; ऐसे में try await Rive(file: file) इस्तेमाल करें और timeout पर भरोसा करें, जिसे एनिमेशन की लंबाई के बराबर सेट करें।
Charmotion सेट में इस तरह के वन-शॉट इंट्रो के लिए एक अलग LaunchStateMachine शामिल होता है: यह 2.6 सेकंड का launch एनिमेशन एक बार चलाता है और idle पोज़ पर खत्म होता है। इसीलिए सैंपल कोड बस उतनी ही देर इंतज़ार करता है।
लॉन्च स्क्रीन और पहले view के बीच फ़्लैश से कैसे बचें?
जब लॉन्च स्क्रीन और इंट्रो का पहला फ़्रेम अलग होते हैं, तब एक साफ़ दिखने वाला "blink" आता है। इसे जड़ से ठीक करें:
- दोनों के लिए एक ही named color इस्तेमाल करें। Info.plist में
UILaunchScreen→UIColorNameको asset catalog के किसी color (यहाँLaunchBackground) पर सेट करें, और इंट्रो के पीछे वहीColor("LaunchBackground")इस्तेमाल करें। इससे दोनों light और dark mode में बिल्कुल एक जैसे बदलते हैं। - इमेज की पोज़िशन मिलाएँ। अगर लॉन्च स्क्रीन पर कोई इमेज (
UIImageName) दिखती है, तो इंट्रो का पहला फ़्रेम कैरेक्टर को उसी साइज़ और उसी सेंटर पर रखकर बनाएँ। सबसे आसान तरीका: लॉन्च स्क्रीन पर सिर्फ़ color रखें और कैरेक्टर को इंट्रो में आने दें। - पहले फ़्रेम को ब्लॉक न करें।
App.initया किसी view केinitमें भारी synchronous काम पहले फ़्रेम में देरी करता है और स्टैटिक लॉन्च स्क्रीन को लंबा कर देता है। इसे.taskमें ले जाएँ। - लोड होने तक Rive view को ट्रांसपेरेंट रखें।
RiveUIViewRepresentable(rive: nil)कुछ भी draw नहीं करता, इसलिए फ़ाइल तैयार होने तक सिर्फ़ बैकग्राउंड color दिखता है।
लॉन्च स्क्रीन तय करने पर Apple की गाइड में Info.plist keys और storyboard विकल्प विस्तार से बताए गए हैं।
स्प्लैश एनिमेशन कितना लंबा होना चाहिए?
Apple ऐप के अंदर वाले इंट्रो के लिए कोई अवधि तय नहीं करता, लेकिन उसकी गाइडेंस एक ही दिशा में इशारा करती है: लॉन्च तुरंत महसूस होना चाहिए, और लॉन्च स्क्रीन इसलिए होती है कि ऐप तैयार दिखे, ब्रांडिंग के लिए नहीं। इंट्रो को भी इसी नज़र से देखें।
- इसे असली काम के साथ चलाएँ। अगर इंट्रो authentication या पहले network fetch को ढकता है, जिसमें वैसे भी उतना समय लगता, तो यूज़र का कुछ नुकसान नहीं होता।
- हर cold launch पर इसे एक ही बार चलाएँ। बैकग्राउंड से लौटने पर सीधे ऐप दिखना चाहिए।
- टैप से स्किप करने दें। इंट्रो में
.onTapGesture { onFinish() }जोड़ें। - सिर्फ़ पहले लॉन्च पर दिखाने के बारे में सोचें।
@AppStorageसे एक flag सेव करें और इंट्रो सिर्फ़ पहली बार या किसी अपडेट के बाद दिखाएँ।
आम तौर पर loading और feedback एनिमेशन की टाइमिंग के लिए एनिमेशन की अवधि और loading UX देखें, और cross-platform तस्वीर के लिए ऐप लॉन्च और स्प्लैश स्क्रीन एनिमेशन देखें।
Reduce Motion चालू होने पर इंट्रो कैसे स्किप करें?
ऊपर वाला RootView यह पहले से करता है: जब accessibilityReduceMotion true होता है, तो if showIntro && !reduceMotion इंट्रो को कभी जोड़ता ही नहीं। यूज़र स्टैटिक लॉन्च स्क्रीन से सीधे ContentView पर पहुँचता है, जो पहले से उससे मेल खाता है।
अगर इंट्रो loading को भी छिपाता है, तो उसे हटाने के बजाय एक स्टिल वर्ज़न से बदलें: वही बैकग्राउंड और कैरेक्टर का एक स्टैटिक पोज़, जो loading पूरी होने पर छोटे fade के साथ हट जाए। Reduce Motion का मकसद मूवमेंट हटाना है, खाली स्क्रीन दिखाना नहीं।
if showIntro {
if reduceMotion {
StaticIntro() // background + still pose
.transition(.opacity)
.zIndex(1)
} else {
IntroView { withAnimation { showIntro = false } }
.transition(.opacity)
.zIndex(1)
}
}
इंट्रो कैरेक्टर को .accessibilityHidden(true) से VoiceOver से छिपाएँ; screen reader इस्तेमाल करने वालों के अनुभव में यह कुछ नहीं जोड़ता।
iOS लॉन्च स्क्रीन के बाद एनिमेटेड इंट्रो जोड़ें
- लॉन्च स्क्रीन कॉन्फ़िगर करें. Info.plist में
UILaunchScreenसेट करें, जिसमेंUIColorNameकिसी asset catalog color की ओर इशारा करे। - root ZStack बनाएँ. नीचे
ContentView()रखें और जब तकshowIntrotrue है, ऊपर एकIntroViewरखें। - पहला फ़्रेम मिलाएँ. इंट्रो के पीछे वही named color इस्तेमाल करें, और लोड होने तक Rive view को खाली रखें।
- इंट्रो चलाएँ. नए runtime से launch .riv लोड करें। अगर आपकी फ़ाइल कोई end ट्रिगर फ़ायर करती है, तो
stream(of:)से उसका इंतज़ार करें; Charmotion काLaunchStateMachineकोई ट्रिगर फ़ायर नहीं करता, इसलिए उसकी 2.6 सेकंड की लंबाई तक इंतज़ार करें। दोनों ही स्थितियों में timeout fallback रखें। - Fade out करें.
withAnimationके अंदरshowIntro = falseसेट करें, और इंट्रो पर.transition(.opacity)और.zIndex(1)लगाएँ। - Reduce Motion सेटिंग का ध्यान रखें. जब
accessibilityReduceMotiontrue हो, तो इंट्रो स्किप करें या उसका स्टिल वर्ज़न दिखाएँ।
अक्सर पूछे जाने वाले सवाल
क्या मैं iOS लॉन्च स्क्रीन के रूप में GIF या वीडियो इस्तेमाल कर सकता हूँ?
नहीं। लॉन्च स्क्रीन Info.plist के ज़रिए सिर्फ़ एक स्टैटिक color और इमेज, या बिना custom code वाला storyboard सपोर्ट करती है, और सिस्टम इसे आपके ऐप के चलने से पहले render करता है। इसके बजाय GIF, वीडियो, Lottie या Rive एनिमेशन अपने पहले view में चलाएँ, जो लॉन्च स्क्रीन जैसे ही रूप से शुरू हो।
मेरा स्प्लैश एनिमेशन पहले सफ़ेद फ़्लैश क्यों करता है?
लॉन्च स्क्रीन का बैकग्राउंड और इंट्रो का पहला फ़्रेम अलग होते हैं, अक्सर इसलिए कि एक fixed color इस्तेमाल करता है और दूसरा dark mode के हिसाब से बदलता है। UILaunchScreen के UIColorName और इंट्रो बैकग्राउंड के लिए एक ही asset catalog color इस्तेमाल करें, और भारी काम App.init से बाहर रखें।
क्या स्प्लैश इंट्रो हर बार ऐप खुलने पर चलना चाहिए?
आमतौर पर सिर्फ़ cold launch पर, और कई ऐप इसे पहले लॉन्च या किसी अपडेट के बाद तक सीमित रखते हैं। बैकग्राउंड से लौटने पर ऐप तुरंत दिखना चाहिए। यूज़र को टैप से इसे स्किप करने दें, और Reduce Motion चालू होने पर इसे अपने-आप स्किप करें।