Problem
I’m trying to implement a language change feature in my SwiftUI app where when a user selects a new language, the app should restart and appear in the selected language. Currently, the language changes, but the UI doesn’t fully update until the app is manually restarted.
Current Implementation
Language Selection View
struct LanguageSelectionView: View {
@Environment(.dismiss) private var dismiss
@Environment(.tabBarVisibility) var tabBarVisibility
@StateObject private var localizationManager = LocalizationManager.shared
@State private var selectedLanguage: Language
@State private var showConfirmation = false
@State private var languageToChange: Language?
@AppStorage("restartKey") var restartKey: Bool = false
var body: some View {
NavigationView {
// ... UI implementation ...
.alert(isPresented: $showConfirmation) {
Alert(
title: Text("Change Language"),
message: Text("The app needs to restart to change the language. Do you want to continue?"),
primaryButton: .default(Text("Yes")) {
if let language = languageToChange {
LocalizationManager.shared.setLanguage(language)
restartKey.toggle() // This should trigger app restart
}
},
secondaryButton: .cancel()
)
}
}
}
}
Localization Manager
class LocalizationManager: ObservableObject {
static let shared = LocalizationManager()
@Published var currentLanguage: Language {
didSet {
UserDefaults.standard.set(currentLanguage.rawValue, forKey: "selectedLanguage")
Bundle.setLanguageBundle(currentLanguage)
NotificationCenter.default.post(name: Notification.Name("LanguageChanged"), object: nil)
}
}
@AppStorage("restartKey") var restartKey: Bool = false
func setLanguage(_ language: Language) {
guard language != currentLanguage else { return }
UserDefaults.standard.set([language.rawValue], forKey: "AppleLanguages")
UserDefaults.standard.set(language.rawValue, forKey: "selectedLanguage")
UserDefaults.standard.synchronize()
currentLanguage = language
// Attempt to trigger restart
DispatchQueue.main.async {
self.restartKey.toggle()
}
}
}
Main App
@main
struct FaceAIEditApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var authManager = AuthenticationManager.shared
@StateObject private var localizationManager = LocalizationManager.shared
@AppStorage("restartKey") var restartKey: Bool = false
init() {
// Set initial language bundle on app launch
// TabBar appearance configuration
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithOpaqueBackground()
tabBarAppearance.shadowColor = .clear
tabBarAppearance.backgroundColor = .clear
UITabBar.appearance().standardAppearance = tabBarAppearance
if #available(iOS 15.0, *) {
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
}
var body: some Scene {
WindowGroup {
ZStack {
if authManager.isLoading {
// Show loading screen while checking auth state
ProgressView()
.scaleEffect(1.5)
.progressViewStyle(CircularProgressViewStyle(tint: .blue))
} else {
MainTabView()
.id(restartKey) // This triggers view rebuild on language change
.environment(.locale, .init(identifier: localizationManager.currentLanguage.rawValue))
}
}
.environmentObject(authManager)
.environmentObject(localizationManager) // Add localizationManager as environment object
.environment(.language, localizationManager.currentLanguage.rawValue)
.onAppear {
authManager.checkAndSignInAnonymously()
}
}
}
}
Issue
When changing the language:
- The language is saved correctly,
- Some UI elements don’t update,
- Restarting the app manually updates it,
Question
How can I programmatically restart the entire app when the user selects a new language so that:
- The app fully restarts,
- All views are updated to the new language,
- The app returns to its initial state (root view),
- The change is smooth and user-friendly
I’ve Tried:
- Using
@AppStorage("restartKey")
to trigger updates, - Posting notifications for language changes,
- Force-updating views with the
.id
modifier, - Various UIApplication window manipulations
Environment
- iOS 15+
- Swift 5.5
- SwiftUI
- Xcode 14
2
Answers
I’m using the following extension:
if you need to use OS’s locale just call
.forceLocale(nil)
usage:
no app restart needed to apply this setting.
Works well as you see:
On apps ive worked on, RootView would listen to notification center after user has selected a new language.
Then paired with id modifier, the rootview get rebuilt with the new language.
here is a sample code.