iOS国际化之语言切换

有关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
}
复制代码

注:xxxViewControllerUINavigationControllerrootViewController,重新加载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开发之应用内快速切换语言包(不跟随系统语言,不用重启)

你可能感兴趣的:(iOS国际化之语言切换)