SwiftUI और UIKit में Reduce Motion (मोशन कम करें)
Charmotion टीम द्वारा · अपडेट:

SwiftUI में @Environment(\.accessibilityReduceMotion) var reduceMotion पढ़ें; यूज़र के सेटिंग बदलते ही यह तुरंत अपडेट होता है। UIKit में UIAccessibility.isReduceMotionEnabled चेक करें और UIAccessibility.reduceMotionStatusDidChangeNotification observe करें। सेटिंग ऑन होने पर लूपिंग या बड़े मूवमेंट की जगह स्थिर पोज़ या crossfade दिखाएँ, और Rive व Lottie views को pause करें।
मुख्य बातें
accessibilityReduceMotioniOS 13, watchOS 6 और visionOS 1 से उपलब्ध है;isReduceMotionEnablediOS 8 से।- सेटिंग बदलने पर SwiftUI अपने आप दोबारा रेंडर करता है; UIKit को change notification चाहिए।
- Rive के नए रनटाइम में
RiveUIViewRepresentableपर.paused(_:)औरRiveUIViewपरisPausedहै। - स्टेट बदलाव रखें, मूवमेंट हटाएँ: मैस्कॉट को "success" अब भी एक पोज़ के रूप में दिखाना चाहिए, गायब नहीं होना चाहिए।
- सजावटी इंट्रो और वन-शॉट सेलिब्रेशन एनिमेशन पूरी तरह छोड़ दें।
SwiftUI में Reduce Motion कैसे पढ़ें?
किसी भी view में environment value declare करें। जब Settings → Accessibility → Motion → Reduce Motion ऑन हो, तो यह true होता है, और इसके बदलने पर SwiftUI body को फिर से evaluate करता है।
struct PulsingBadge: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var saved = 0
var body: some View {
Image(systemName: "checkmark.seal.fill")
.font(.largeTitle)
.phaseAnimator([false, true], trigger: saved) { content, lifted in
content.scaleEffect(lifted && !reduceMotion ? 1.2 : 1)
}
.onTapGesture { saved += 1 }
}
}
explicit एनिमेशन के लिए पूरे view को branch करने के बजाय हल्का एनिमेशन चुनें या कोई एनिमेशन न रखें:
withAnimation(reduceMotion ? nil : .spring(duration: 0.4, bounce: 0.3)) {
isExpanded.toggle()
}
// Or swap movement for a fade
.transition(reduceMotion ? .opacity : .move(edge: .bottom))
बड़े सिद्धांत (मोशन किसे माना जाता है, WCAG 2.3.3) एक्सेसिबल एनिमेशन और मोशन कम करें में कवर हैं; यह गाइड Apple प्लेटफ़ॉर्म के कोड पर फ़ोकस करती है।
UIKit में Reduce Motion कैसे पढ़ें और बदलाव कैसे सुनें?
UIAccessibility.isReduceMotionEnabled मौजूदा वैल्यू देता है। यह आपको सूचित नहीं करता, इसलिए पहले से दिख रही स्क्रीन अपडेट करने के लिए UIAccessibility.reduceMotionStatusDidChangeNotification observe करें:
import UIKit
import RiveRuntime
final class MascotViewController: UIViewController {
private let riveView = RiveUIView(rive: nil)
private let stillView = UIImageView(image: UIImage(named: "mascot-idle"))
private var motionObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
// ... add riveView and stillView in the same frame, load the Rive file ...
applyMotionPreference()
motionObserver = NotificationCenter.default.addObserver(
forName: UIAccessibility.reduceMotionStatusDidChangeNotification,
object: nil,
queue: .main
) { [weak self] _ in
Task { @MainActor in self?.applyMotionPreference() }
}
}
deinit {
if let motionObserver { NotificationCenter.default.removeObserver(motionObserver) }
}
private func applyMotionPreference() {
let reduce = UIAccessibility.isReduceMotionEnabled
riveView.isPaused = reduce
riveView.isHidden = reduce
stillView.isHidden = !reduce
}
}
UIView.animate कॉल में, कॉल के समय flag चेक करें और छोटा cross-dissolve इस्तेमाल करें, या बिना एनिमेशन के फ़ाइनल स्टेट सेट करें।
Reduce Motion ऑन होने पर मैस्कॉट को क्या करना चाहिए?
मूवमेंट हटाएँ, लेकिन मतलब बनाए रखें। जो मैस्कॉट स्टेट बताता है ("लोड हो रहा है", "हो गया", "कुछ गड़बड़ हो गई"), उसे अब भी वह बताना चाहिए।
| पूरा मोशन | कम मोशन वाला विकल्प |
|---|---|
| idle लूप (साँस लेना या पलक झपकाना) | स्थिर idle पोज़ |
| लोडिंग लूप | स्थिर "thinking" पोज़, साथ में टेक्स्ट या progress indicator |
| success / error वन-शॉट | छोटे fade के साथ happy या sad स्थिर इमेज पर स्विच करें, साथ में टेक्स्ट लेबल |
| enter एनिमेशन (उड़कर आना, उछलना) | crossfade से दिखाना |
| लॉन्च इंट्रो | छोड़ दें, या loading के दौरान एक स्थिर फ़्रेम |
| नए आइटम के लिए attention लूप | स्थिर attention पोज़ और एक badge |
इसे लागू करने का सबसे आसान तरीका है मूड से स्थिर इमेज तक की एक dictionary, जिसमें उसी कैरेक्टर के फ़्रेम हों, ताकि कम मोशन वाला वर्ज़न जानबूझकर बनाया हुआ लगे:
struct MascotView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@ObservedObject var character: CharacterModel // Rive controller that keeps the view model instance
let mood: Int
var body: some View {
ZStack {
if reduceMotion {
Image("mascot-mood-\(mood)")
.resizable()
.scaledToFit()
.id(mood)
.transition(.opacity)
} else {
RiveUIViewRepresentable(rive: character.rive)
}
}
.animation(.easeInOut(duration: 0.2), value: mood)
.onChange(of: mood) { _, newMood in character.setMood(newMood) }
}
}
Reduce Motion में Rive या Lottie को pause कैसे करें?
pause करने से मौजूदा फ़्रेम रुक जाता है, जो सजावटी लूप के लिए ठीक है अगर वह फ़्रेम एक पोज़ जैसा लगे। हर रनटाइम में इसके लिए सीधा स्विच है:
// Rive, new Apple runtime (SwiftUI)
RiveUIViewRepresentable(rive: character.rive)
.paused(reduceMotion)
// Rive, new runtime (UIKit)
riveView.isPaused = UIAccessibility.isReduceMotionEnabled
// Rive, legacy runtime
reduceMotion ? riveViewModel.pause() : riveViewModel.play()
// Lottie
LottieView(animation: .named("idle"))
.playbackMode(reduceMotion
? .paused(at: .progress(0))
: .playing(.fromProgress(0, toProgress: 1, loopMode: .loop)))
दो सावधानियाँ:
- pause किया गया फ़्रेम बीच मूवमेंट में हो सकता है। अगर आप Rive view को चलने के बाद pause करते हैं, तो वह किसी अटपटे बीच के पल पर रुक सकता है। pause से पहले मूड को शांत स्टेट (idle) पर सेट करने से, या इसकी जगह स्थिर इमेज दिखाने से यह नहीं होता।
- pause के दौरान ट्रिगर फ़ायर न करें। pause की गई स्टेट मशीन आगे नहीं बढ़ती, इसलिए वन-शॉट रिएक्शन नहीं चलेंगे। इसकी जगह नतीजा स्थिर इमेज या टेक्स्ट में दिखाएँ।
Lottie 4.3.0 और उसके बाद के वर्ज़न Reduce Motion ऑन होने पर reduced motion नाम का marker अपने आप चला सकते हैं, अगर डिज़ाइनर ने फ़ाइल में ऐसा marker रखा हो।
क्या Reduce Motion में भी लॉन्च इंट्रो दिखाना चाहिए?
नहीं। इंट्रो परिभाषा से ही सजावटी होता है, और जिन यूज़र्स ने यह सेटिंग ऑन की है उनके लिए इसे छोड़ना सबसे साफ़ फ़ायदा है। overlay को environment value के आधार पर दिखाएँ:
struct RootView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var showIntro = true
var body: some View {
ZStack {
ContentView()
if showIntro && !reduceMotion {
IntroView { withAnimation { showIntro = false } }
.transition(.opacity)
.zIndex(1)
}
}
}
}
पूरा पैटर्न, जिसमें स्टैटिक लॉन्च स्क्रीन से मेल बैठाना भी शामिल है, SwiftUI में एनिमेटेड स्प्लैश स्क्रीन में है।
अगर कैरेक्टर का आपके ऐप में कोई मतलब है, तो स्टिल पोज़ सीधे उसके एनिमेशन सेट से आ सकते हैं: हर पेड प्लान पर हर सेट में .riv के साथ हर एनिमेशन का एक स्टिल पोस्टर होता है, और सभी 17 एनिमेशन MP4, GIF और ट्रांसपेरेंट WebM/MOV के रूप में ज़रूरत पर एक्सपोर्ट होते हैं, ताकि हर मूड के लिए मेल खाता स्टिल मिले।
ट्रांज़िशन और scroll इफ़ेक्ट पर Reduce Motion कैसे लागू करें?
कैरेक्टर एनिमेशन किसी ऐप के मोशन का सिर्फ़ एक हिस्सा है। सिस्टम के दिए ट्रांज़िशन संभालना प्लेटफ़ॉर्म का काम है, लेकिन आपके जोड़े गए हर कस्टम इफ़ेक्ट को ढालने की ज़िम्मेदारी आपकी है।
- Insert और remove ट्रांज़िशन:
.move,.slideऔर.scaleकी जगह.opacityइस्तेमाल करें। - Scroll पर चलने वाले इफ़ेक्ट (
scrollTransition, parallax header): मोशन कम करते समय identity इफ़ेक्ट लौटाएँ। matchedGeometryEffectवाले hero एनिमेशन: स्टेट बदलाव रहने दें, लेकिनwithAnimationमेंnilपास करके एनिमेशन हटा दें।- Symbol इफ़ेक्ट: bounce की जगह ऐसे इफ़ेक्ट चुनें जिनमें हलचल न हो, जैसे रंग बदलना।
struct CardRow: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
let title: String
var body: some View {
Text(title)
.padding()
.scrollTransition { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0.6)
.scaleEffect(reduceMotion || phase.isIdentity ? 1 : 0.92)
}
}
}
एक छोटा helper पूरे ऐप में यह चुनाव एक जैसा रखता है:
extension Animation {
static func motionAware(_ animation: Animation, reduceMotion: Bool) -> Animation? {
reduceMotion ? nil : animation
}
}
withAnimation(Animation.motionAware(.bouncy, reduceMotion: reduceMotion)) { selected = item }Reduce Motion को जल्दी से टेस्ट कैसे करें?
तीन तरीके, सबसे तेज़ पहले:
- SwiftUI preview:
accessibilityReduceMotionget-only है, इसलिए.environment(\.accessibilityReduceMotion, true)compile नहीं होता। वैल्यू को parent में पढ़ें और जिस view का प्रीव्यू देखना है उसमेंreduceMotion: Boolparameter पास करें, फिर दोनों वैल्यू का प्रीव्यू साथ-साथ देखें। - Accessibility Inspector: Xcode → Open Developer Tool → Accessibility Inspector पर जाएँ, simulator चुनें, और ऐप चलते समय इसके settings panel में Reduce Motion को on/off करें।
- Simulator या डिवाइस की settings: Settings → Accessibility → Motion → Reduce Motion।
हर स्क्रीन को दोनों स्टेट में जाँचें: लूप रुकते हैं, कोई ज़रूरी चीज़ गायब नहीं होती, और स्क्रीन खुली रहते हुए setting बदलने पर वह अपडेट होती है (SwiftUI यह अपने आप करता है; UIKit तभी, जब आप notification को observe करें)।
अक्सर पूछे जाने वाले सवाल
क्या accessibilityReduceMotion watchOS और visionOS पर उपलब्ध है?
हाँ। Apple के अनुसार SwiftUI की accessibilityReduceMotion environment value iOS 13 और उसके बाद, watchOS 6 और उसके बाद, और visionOS 1 पर उपलब्ध है। UIKit में UIAccessibility.isReduceMotionEnabled iOS 8 से उपलब्ध है, और reduceMotionStatusDidChangeNotification से आपको पता चलता है कि आपका ऐप चलते समय यूज़र ने setting कब बदली।
क्या Reduce Motion से सारा एनिमेशन बंद हो जाना चाहिए?
नहीं। यह मोशन कम करने को कहता है, खासकर बड़ी, लूपिंग या parallax हलचल। छोटे fade, रंग बदलना और स्टेट बदलना आमतौर पर ठीक हैं। वह feedback बनाए रखें जो यूज़र को बताता है कि कुछ हुआ, बस उसे बिना खिसकाए, बड़ा-छोटा किए या उछाले दिखाएँ, ताकि setting on करने की वजह से किसी की कोई जानकारी न छूटे।
क्या Rive अपने आप Reduce Motion का पालन करता है?
नहीं। Rive रनटाइम वही चलाता है जो स्टेट मशीन करती है। accessibilityReduceMotion या UIAccessibility.isReduceMotionEnabled को खुद पढ़ें और view को .paused(_:) या isPaused से रोकें, या उसे मौजूदा मूड की स्थिर इमेज से बदल दें। रोकने से पहले कोई शांत मूड सेट करें, ताकि रुका हुआ फ़्रेम एक पोज़ जैसा लगे।
इस गाइड से झटपट जवाब
स्रोत
- accessibilityReduceMotion, Apple
- UIAccessibility.isReduceMotionEnabled, Apple
- UIAccessibility.reduceMotionStatusDidChangeNotification, Apple
- Apple runtime (new and legacy), Rive
- RiveUIViewRepresentable.swift, Rive (GitHub)
- lottie-ios 4.3.0 release notes, Airbnb (GitHub)