有关App的国际化的文章,网上也比较多,我这里不作过多的赘叙,总结起来主要是以下几方面,
1、文本的国际化
2、storyboard & xib的国际化
3、image的国际化
想了解上面详情的可以见本文下面的参考文章,他们讲的图文并茂已经非常详细了,本文想要探讨的是App内多语言的切换的一个问题。
在语言切换之后,对于未加载的页面会在加载的时候进行本地化,但是对于已经加载的viewController呢,如何在不重启应用的情况,完成语言切换呢,有下面几个方面需要考虑,
1、当前UIViewController需要切换语言;
2、上一层ViewController也需要切换语言,依次类推到rootViewController需要切换语言;
3、storyboard & xib内的Main.string需要切换语言
这3个问题就是本文需要实验解决的问题,咱们一个一个来。
第1个问题解决方式很多,语言设置页面使用NSLocalizedString
,如果采用UITableview
的话,reload一下即可,其他的方式挨个替换也可以。
对第2个问题和第1个问题类似,如果语言设置不是二级或三级页面的话,而是一级页面的话,那可以合并为一个问题,下面是在网上一开始找到的方案:
/* 采取storyboard方式 */
let mainSb = UIStoryboard(name: "Main", bundle: nil)
let rootViewC = mainSb.instantiateInitialViewController() as! UITabBarController
rootViewC.selectedIndex = 1 //回到设置页面
UIApplication.shared.delegate!.window!!.rootViewController = rootViewC
复制代码
/*纯代码方式*/
ZHTabBarController *tab = [[ZHTabBarController alloc] init];
[UIApplication sharedApplication].keyWindow.rootViewController = tab;
// 跳转到设置页
tab.selectedIndex = 2;
复制代码
上面的方案就是直接重新创建UIViewController,实验表明可以不重启App的情况完成语言切换,但是上面的代码是实验代码,实际上App的语言切换一般都在2级页面或3级页面,采用上面的方法之后,页面直接回到一级页面了,想要恢复ViewController的Stack该怎么解决呢?简单直接的方案就是重新创建了,和上面方式相同,但是这样是非常不灵活的方式。
我在上面的基础上进行了一些改造,如果是tabbarcontroller的请参考自行修改,下面是改造的核心代码,实验结果可以完成语言切换。
func resetBootViewController() {
let main = UIStoryboard(name: "Main", bundle: nil)
guard let rootVC = main.instantiateInitialViewController() as? UINavigationController else {
return
}
/*
* 重新加载rootviewcontroller,由于stack中还有viewcontroller,所以需要添加到新的viewcontrollers中,同时需要filter掉原来的rootviewcontroller
*/
if let nav = appDelegate.window?.rootViewController as? UINavigationController {
let vcs = nav.viewControllers
let result = vcs.filter { (controller) -> Bool in
if controller.isMember(of: xxxViewController.classForCoder()) {
return false
}
return true
}
for controller in result {
let className = NSStringFromClass(controller.classForCoder).components(separatedBy: ".").last!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newController = storyboard.instantiateViewController(withIdentifier: className)
rootVC.viewControllers.append(newController)
}
}
appDelegate.window?.rootViewController = rootVC
}
复制代码
注:xxxViewController
是 UINavigationController
的 rootViewController
,重新加载rootviewcontroller
,由于stack中还有viewcontroller,所以需要添加到新的viewcontrollers中,同时需要filter掉原来的rootviewcontroller
,添加上面的逻辑之后,即可恢复当前视图栈。
采取上面方法后,其实还有一个地方需要留意,就是如果UIApplication.shared.keyWindow
上此时如果含有subView的话,那么他们会被destory掉,如果需要恢复的话,需要代码里重新加载一次。
第3个问题,参考文章里面的解决方案基本一致,实验结果表明是可行的,我这里引用一下他们的代码,
/**
* 当调用onLanguage后替换掉mainBundle为当前语言的bundle
*/
class BundleEx: NSBundle {
override func localizedStringForKey(key: String, value: String?, table tableName: String?) -> String {
if let bundle = languageBundle() {
return bundle.localizedStringForKey(key, value: value, table: tableName)
}else{
return super.localizedStringForKey(key, value: value, table: tableName)
}
}
}
extension NSBundle {
/*
private struct Static {
static var onceToken : dispatch_once_t = 0
}
func onLanguage(){
//替换NSBundle.mainBundle()为自定义的BundleEx
dispatch_once(&Static.onceToken) {
object_setClass(NSBundle.mainBundle(), BundleEx.self)
}
}
*/
private static var onLanguageDispatchOnce: ()->Void = {
//替换Bundle.main为自定义的BundleEx
object_setClass(Bundle.main, BundleEx.self)
}
func onLanguage(){
Bundle.onLanguageDispatchOnce()
}
//当前语言的bundle
func languageBundle()->NSBundle?{
return Languager.standardLanguager().currentLanguageBundle
}
}
复制代码
由于swift3之后废弃了dispatchOnce的方法,所以上面参考[iOS 多国语言本地化与App内语言切换(Swift)] 进行了修改,两个思路是相同的。
有了上面的思路,就完成了App内的语言切换。
上面第2个问题虽然解决了,但是始终觉得不是特别的好,总觉得应该有更好更简单的解决方式,所幸功夫不负有心人,今天在【参考文章6】里找到了,关键的一句话这里view置为nil,当视图再次显示的时候会重新走viewDidLoad方法
。下面是采取新思路的核心代码。
func resetBootViewController() {
if let nav = appDelegate.window?.rootViewController as? UINavigationController {
for controller in nav.viewControllers {
if controller.isViewLoaded && controller.view.window == nil {
//这里置为nil,当视图再次显示的时候会重新走viewDidLoad方法
controller.view = nil
}
}
}
}
复制代码
看看效果图:
后记: 上面的方案虽然完成了语言切换效果,并且不会destory掉UIApplication.shared.keyWindow
上的view,但是依然有些限制,比如我遇到一个问题,在UINavigationController的RootViewController中采用如下方式定义了一个button。
lazy var addButton: UIButton = {
let title = LangHelper.getString(key: "Adding")
var addButton = UIButton(type: UIButtonType.custom)
addButton.backgroundColor = UIColor.init(hexString: "#4b5cc4")
addButton.setTitle(title, for: UIControlState.normal)
addButton.setTitleColor(UIColor.white, for: UIControlState.normal)
addButton.setTitleColor(UIColor.lightGray, for: UIControlState.highlighted)
return addButton
}()
复制代码
结果上面方案设置view为nil之后,实际上addButton并没有得到重新执行,所以这个button的title并没有切换成功,需要在viewDidLoad里面设置title才行。
还有一个问题就是navigationbar的backbutton的title问题,上面的方式并没有成功切换返回按钮的标题,所以不得不去掉返回按钮的titile,self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
。
如果你发现新的解决方案,还要留言。
注:原创文章,如转载请告知作者,并注明作者和来源。
参考文章:
1、iOS国际化
2、iOS 多国语言本地化与App内语言切换(Swift)
3、iOS App的国际化,以及App内的语言切换
4、iOS应用内语言切换功能
5、IOS APP 国际化实现不跟随系统
6、 iOS开发之应用内快速切换语言包(不跟随系统语言,不用重启)