How to reload content text after change language iOS Swift code - ios

I have a problem when I change the application language using swift code. In my case I had to use the xliff file that was automatically generated from the storyboard/xib.
My code:
let APPLE_LANGUAGE_KEY = "AppleLanguages"
/// L102Language
class L102Language {
/// get current Apple language
class func currentAppleLanguage() -> String{
let userdef = UserDefaults.standard
let langArray = userdef.object(forKey: APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
return current
}
/// set #lang to be the first in Applelanguages list
class func setAppleLAnguageTo(lang: String) {
let userdef = UserDefaults.standard
userdef.set([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}
}
Use:
if L102Language.currentAppleLanguage() == "en" {
L102Language.setAppleLAnguageTo(lang: "vi")
UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
L102Language.setAppleLAnguageTo(lang: "en")
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}
After userdef.synchronize() is executed the application does not change the language. It only really works when I restart the app. I think this way is not good. In this case, what else do I need to do to change the language of the application without restarting.
thanks everyone
Update:
I resolved problem with answer https://stackoverflow.com/a/48187049/12429634
Thanks everyone!

You will need to register for the NSLocale currentLocaleDidChangeNotification, and write code to update your UI when you get notified.

Related

IOS editor bug. archivedData renamed

Please help me! I am stuck in a loop and can't find my way out. I am trying to learn IOS programming for work so I thought I would start with their tutorial app the Meal list application. I am at the part where you are supposed to start saving persistent data and now the editor has me stuck in a never ending loop. I have a line of code...
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)
That gives me a warning that says...
'archiveRootObject(_:toFile:)' was deprecated in iOS 12.0: Use
+archivedDataWithRootObject:requiringSecureCoding:error: instead
OK, so I change the line of code to...
let isSuccessfulSave = NSKeyedArchiver.archivedDataWithRootObject(meals)
Which then gives me the warning...
'archivedDataWithRootObject' has been renamed to
'archivedData(withRootObject:)'
OK, so I change the line of code to...
let isSuccessfulSave = NSKeyedArchiver.archivedData(withRootObject: meals)
Which tells me...
'archivedData(withRootObject:)' was deprecated in iOS 12.0: Use
+archivedDataWithRootObject:requiringSecureCoding:error: instead
OK... So... archivedData was deprecated and I have to use archivedDataWithRootObject, but using archivedDataWithRootObject has been renamed to archivedData, but archivedData is deprecated so use archivedDataWithRootObject which is renamed to archivedData which is deprecated... ad infinitum.
I have tried looking on the developer docs but they just tell me the same thing, one is deprecated, with no links or anything and searching google just gives me a bunch of pages showing me the syntax of using any of them. I am still really new to IOS programming and have no idea how to get out of this endless loop of deprecated to renamed to deprecated to...
Please help, I am lost and not sure how to continue. Thank you.
I am following the same example you are trying to do, and I figured out how to update the methods for storing and retrieving values in iOS 12, this should help you:
//MARK: Private Methods
private func saveMeals() {
let fullPath = getDocumentsDirectory().appendingPathComponent("meals")
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: false)
try data.write(to: fullPath)
os_log("Meals successfully saved.", log: OSLog.default, type: .debug)
} catch {
os_log("Failed to save meals...", log: OSLog.default, type: .error)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
private func loadMeals() -> [Meal]? {
let fullPath = getDocumentsDirectory().appendingPathComponent("meals")
if let nsData = NSData(contentsOf: fullPath) {
do {
let data = Data(referencing:nsData)
if let loadedMeals = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Array<Meal> {
return loadedMeals
}
} catch {
print("Couldn't read file.")
return nil
}
}
return nil
}
Also you will find that you need to update ViewDidLoad as this:
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button item provided by the table view controller.
navigationItem.leftBarButtonItem = editButtonItem
let savedMeals = loadMeals()
if savedMeals?.count ?? 0 > 0 {
meals = savedMeals ?? [Meal]()
} else {
loadSampleMeals()
}
}
I hope this helps, for me the app is now working, storing and retrieving data.
FYI: This doesn't work with Xcode 11 beta and iOS 13 is should work with anything before those versions.
A general solution for iOS 12 would be:
class SettingsArchiver {
static func setData(_ value: Any, key: String) {
let ud = UserDefaults.standard
let archivedPool = try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
ud.set(archivedPool, forKey: key)
}
static func getData<T>(key: String) -> T? {
let ud = UserDefaults.standard
if let val = ud.value(forKey: key) as? Data,
let obj = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(val) as? T {
return obj
}
return nil
}
}
You need
try {
let data = try NSKeyedArchiver.archivedData(withRootObject:meals,requiringSecureCoding:true)
try data.write(to:fullPath)
}
catch {
print(error)
}
Here in Docs it's IOS 11+
I would say, the answer directly addressing your question is to use the ArchiveURL defined in your Meal.swift data model (think MVC pattern) and reimplement the saveMeals() function in your MealTableViewController.swift controller using the recommended replacement to the deprecated archiveRootObject method this way:
private func saveMeals(){
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: true)
try data.write(to: Meal.ArchiveURL)
}
catch {
print("Couldn't save to file")
}
}
Although this answer is a little late :-) I hope it helps whomever may come across this issue.

can I localize my iOS app to Spanish or German while keeping the rest of the phone in English?

users want to keep our iOS app language setting totally separate from the user's iPhone language. Basically, inside the app itself, the user selects the App's language, and then the app's UI and text all changes to the selected language. This is more flexible than Apple's localization standards, and they don't address this situation. Is there some agreed upon best practice as to how to do accomplish this?
Specifically, a user might want the app's interface in Spanish, but the rest of the phone remains in English.
Thank you!
Create the Localizable.strings as you do the default localization in iOS. Then use this code to achieve language switch with in the app.
extension String {
func localized() -> String {
let selectedLanguage = UserDefaults.standard.string(forKey:) CURRENT_LANGUAGE)
guard let path = Bundle.main.path(forResource: selectedLanguage == "en" ? "Base" : selectedLanguage, ofType: "lproj") else {
if let basePath = Bundle.main.path(forResource: "Base", ofType: "lproj"){
return Bundle(path: basePath)?.localizedString(forKey: self, value: "", table: nil) ?? self
}
return self
}
return Bundle(path: path)?.localizedString(forKey: self, value: "", table: nil) ?? self
}
}
You can access the localized string by calling let text = "LOGIN_SCREEN_TITLE".localized() where LOGIN_SCREEN_TITLE is added in the localisation file.
enum LanguagesSupported: String {
case English = "en"
case French = "fr"
}
UserDefaults.standard.set(LanguagesSupported.French.rawValue, forKey: CURRENT_LANGUAGE)
First create a dictionary of language code and language names like this
let applicationLanguages = ["de":"German","es":"Spanish"]
then when user can select particular language then after clicking on any button call this function and kill the app.
func setLanguage(languageCode: String){
let languageArray = NSArray(objects: languageCode)
UserDefaults.standard.set(languageArray, forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
UserDefaults.standard.set(true, forKey: "changedLanguage")
UserDefaults.standard.set(languageCode, forKey: "AppLanguage")
}
Then show alert of selected language, then on the click of ok kill the app.
func endBackgroundTask() {
exit(0)
}

Swift 4 Settings Bundle, get defaults

I created a settings bundle with about 8 toggle switches. What I am trying to do it get the default values from the settings bundle. Currently right now I have these two methods:
func registerSettingsBundle(){
let appDefaults = [String:AnyObject]()
UserDefaults.standard.register(defaults: appDefaults)
UserDefaults.standard.synchronize()
}
func updateDisplayFromDefaults(){
let defaults = UserDefaults.standard
let update_lot = defaults.bool(forKey: "update_lot")
print(update_lot)
}
and I am calling these methods in my viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
registerSettingsBundle()
updateDisplayFromDefaults()
}
However this does not get me the default values (which are all true, but they all return false). This works and gives me the correct values if I close down the app, open settings, adjust the settings and re-open the app. Is there away of getting the default settings? I went the route of reading the plist, but if I change the settings in my settings bundle, it would not take effect.
For the sake of the demonstration let's assume that you have two switches in the settings bundle. One with the default value set to YES and one with the default value set to NO.
If you want to be able to access default values defined in the Settings.bundle from the UserDefaults in your app you have to register them first. Unfortunately, iOS won't do it for you and you have to take care of it by yourself.
The following method scans the Root.plist associated with the Settings.bundle and registers the default values for the identifiers of your preferences.
func registerDefaultsFromSettingsBundle()
{
let settingsUrl = Bundle.main.url(forResource: "Settings", withExtension: "bundle")!.appendingPathComponent("Root.plist")
let settingsPlist = NSDictionary(contentsOf:settingsUrl)!
let preferences = settingsPlist["PreferenceSpecifiers"] as! [NSDictionary]
var defaultsToRegister = Dictionary<String, Any>()
for preference in preferences {
guard let key = preference["Key"] as? String else {
NSLog("Key not found")
continue
}
defaultsToRegister[key] = preference["DefaultValue"]
}
UserDefaults.standard.register(defaults: defaultsToRegister)
}
I recommend running it as early as possible. You will be sure that the defaults are there for all parts of your app.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
registerDefaultsFromSettingsBundle()
let one = UserDefaults.standard.bool(forKey: "switch_one")
let two = UserDefaults.standard.bool(forKey: "switch_two")
NSLog("One: \(one), Two: \(two)")
return true
}
this does not get me the default values (which are all true, but they
all return false)
Looks like you have a toggle switch which displays as ON in the setting bundle and when you read bundle you get all the false value.
If this is the case then you are missing something here.
In setting bundle(Root.plist) we have "Default Value" field which is nothing to do with the actual default value of toggle switch. This is just a visual indicator to switch.
You may have "Default value" set as "YES" in plist but when you try to read the value you will end up getting false.
Here I have set Default Value for Reminder in Root.plist as YES and for Update NO
So that when app launch it shows as above.
But when I tried to read these defaults - it gives both as false.
func getDefaults() {
let stanDefaults = UserDefaults.standard
print("Default value of Update - \(stanDefaults.bool(forKey: "update_lot_pref"))")
print("\nDefault value of Reminder - \(stanDefaults.bool(forKey: "reminder_pref"))")
}
Default value of Update - false Default value of Reminder - false
Now, if you want to sync these values - default value in Root.plist and value of default - then you have to set it programmatically.
func setApplicationDefault() {
let stanDefaults = UserDefaults.standard
let appDefaults = ["reminder_pref": true]
stanDefaults.register(defaults: appDefaults)
stanDefaults.synchronize()
}
Here in my Root.plist I have default value as YES and also when viewDidload I set this preference value as true. When I run it gives me
Default value of Reminder - true
And this is how my Root.plist looks.
Hope it helps.
Here's an answer based on #Kamil (many thanks for that) that doesn't rely on NSDictionary and uses PropertyListSerialization.
Swift 5
func registerDefaultsFromSettingsBundle() {
let settingsName = "Settings"
let settingsExtension = "bundle"
let settingsRootPlist = "Root.plist"
let settingsPreferencesItems = "PreferenceSpecifiers"
let settingsPreferenceKey = "Key"
let settingsPreferenceDefaultValue = "DefaultValue"
guard let settingsBundleURL = Bundle.main.url(forResource: settingsName, withExtension: settingsExtension),
let settingsData = try? Data(contentsOf: settingsBundleURL.appendingPathComponent(settingsRootPlist)),
let settingsPlist = try? PropertyListSerialization.propertyList(
from: settingsData,
options: [],
format: nil) as? [String: Any],
let settingsPreferences = settingsPlist[settingsPreferencesItems] as? [[String: Any]] else {
return
}
var defaultsToRegister = [String: Any]()
settingsPreferences.forEach { preference in
if let key = preference[settingsPreferenceKey] as? String {
defaultsToRegister[key] = preference[settingsPreferenceDefaultValue]
}
}
UserDefaults.standard.register(defaults: defaultsToRegister)
}
So for a Root.plist like this:
... the defaultsToRegister would be:
There's also the new compactMapValues() API in Swift 5 that may or may not be helpful here.
Add notification for UserDefaults.didChangeNotification like below:
override func viewDidLoad() {
super.viewDidLoad()
registerSettingsBundle()
NotificationCenter.default.addObserver(self, selector: #selector(updateDisplayFromDefaults), name: UserDefaults.didChangeNotification, object: nil)
updateDisplayFromDefaults()
}
func registerSettingsBundle(){
let appDefaults = [String:AnyObject]()
UserDefaults.standard.register(defaults: appDefaults)
UserDefaults.standard.synchronize()
}
func updateDisplayFromDefaults(){
let defaults = UserDefaults.standard
let update_lot = defaults.bool(forKey: "update_lot")
print(update_lot)
}
I am having a little bit of trouble following exactly what you're asking, but it sounds like you've created a settings bundle plist that specifies that some defaults are "true" if undefined (so that if a user hasn't set them they default to true). But I think when you do:
let update_lot = defaults.bool(forKey: "update_lot")
...there is no way for this code to know that "unset" should evaluate to "true" instead of "false". You could use defaults.object(forkey: "update_lot") instead and if that returns an object, get the boolean value of that object (or just call defaults.bool at that point), but if it returns nil assume that it's true.

Change App Localization Based on User Selection

I've read lots of answers about this, but most are many years old and I don't know what's the latest info.
On first launch, my app will ask the user which language (s)he prefers, rather than just using the OS default language.
How can I set the localization rest of the app to the selected language?
How can I get which language the user has selected in other views?
I thought the following code would set the language, but it didn't do the job:
UserDefaults.standard.set("AR", forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
I have handled this with a LanguageManager singleton class, which handles all the localization. Here is some psuedo code, as its typically a fairly large class.
It has a list of all locales that are supported, for example:
let supportedLocales = ['en-US', 'en-CA', 'fr', 'es-ES', 'es-MX']
It also stores the selected language in UserDefaults. When the selectedLocale changes, it sends a Notification, in case you want to notify your views or anything else:
var selectedLocale: String? {
get {
return UserDefaults.standard.object(forKey: UserDefaultsKeys.selectedLocale) as? String
}
set (newLocale) {
let didChange = self.selectedLocale != newLocale
UserDefaults.standard.set(newLocale, forKey: UserDefaultsKeys.selectedLocale)
UserDefaults.standard.synchronize()
if didChange {
NotificationCenter.default.post(name: Notification.Name.localeDidChange, object: nil)
}
}
}
Now, in order to pull strings out of your localized.strings files, you can't use the standard Apple methods - you have to provide your own. Use LanguageManager.shared.getString(for key:String, alt:String) to reference keys in your .strings file to pull out localized strings.
var selectedLanguage:String? {
//returns just the language portion of the locale - eg: 'en' from 'en-US'
if let selectedLocale = selectedLocale {
return selectedLocale.components(separatedBy: "-")[0]
}
return nil
}
func getString(for key:String, alt:String) -> String
{
var val:String? = getString(for:key, language: selectedLocale)
if val == nil {
val = getString(for:key, language: selectedLanguage)
}
if val == nil {
val = getString(for:key, language: "en") //default to English
}
if let val = val {
return val
}
return alternate //use fallback
}
func getString(for key:String, language:String) -> String?
{
let path = Bundle.main.path(forResource:language, ofType:"lproj")
if let languageBundle = Bundle(path:path) {
return languageBundle.localizedString(for: key)
}
return nil
}
By default is the English Language // You can set Language according you
UserDefaults.standard.set("en", forKey: "Apple")
UserDefaults.standard.synchronize()
If you want to current system language than use code
let langStr = Locale.current.languageCode //and set in UserDeafaults
UserDefaults.standard.set(langStr, forKey: "Apple")
var currentlanguage: String?
self.currentlanguage = UserDefaults.standard.object(forKey: "Apple") as! String?
print("current language ---%#",self.currentlanguage)

How to retrieve the list of all installed keyboards in Swift?

I am trying to write a simple function that will check to see if a specific keyboard is installed.
Here is what I have in the function so far:
func isCustomKeyboardEnabled() {
let bundleID:NSString = "com.company.MyApp.Keyboard"
let installedKeyboards = NSUserDefaults.standardUserDefaults().objectForKey("AppleKeyboards")
println(installedKeyboards)
}
This is what it returns in the console:
Optional((
"en_GB#hw=British;sw=QWERTY",
"emoji#sw=Emoji",
"com.nuance.swype.app.Global-Keyboard",
))
I am having an hard time checking to see if my bundleID is in this returned object. I've tried a for in and anif(contains(x,x)) but it fails to build. Any help would be much appreciated.
Swift 2.0 Solution:
func installedKeyboards(){
if let installedKeyboard = NSUserDefaults.standardUserDefaults().objectForKey("AppleKeyboards") as? [String]{
if installedKeyboard.contains("Your Unique Identifier"){
print("Custom Keyboard Found")
}else{
print("Custom Keyboard Not Installed")
}
}
}
You've got an Optional response there, meaning that the value could be nil. Try doing this instead:
if let installedKeyboards = NSUserDefaults.standardUserDefaults().objectForKey("AppleKeyboards") {
if (contains(installedKeyboards, "Your keyboard") {
// Do stuff.
}
}
Here's the Swift 4 version from Statik answer:
func installedKeyboards() {
if let installedKeyboard = UserDefaults.standard.object(forKey: "AppleKeyboards") as? [String] {
if installedKeyboard.contains("Your Unique Identifier") {
print("Custom Keyboard Found")
}
else {
print("Custom Keyboard Not Installed")
}
}
}

Resources