iOS 专题

IOS多语言切换4-framework中的语言切换和资源使用

2020-06-18  本文已影响0人  肆点壹陆

开发语言:Swift 5.0
开发环境:Xcode 11.5
发布平台:IOS 13

1、Framework创建和资源使用

在IOS多语言切换3文章中,我们已经实现了用户通过App手动设置语言,来设置程序文字和图片的多语言化,但随着App开发的需求,一旦引入了Framework,按照上一章实现的功能,所有在Framework中使用的文字和图片资源,无法随着用户设置的语言进行切换,原因在于每个Framework或者项目都有自己的独立Bundle,上一章中,我们只对Bundle.main包进行了处理,所以接下来,我们要对每个Framework的Bundle都进行多语言的处理。

以以下demo为例,建立一个包含2个framework的项目。

在Main.Storyboard和SubRes.Storyboard中,已经设置好文字的中文与英文,此时运行程序可以通过切换IOS设备的语言来实现App的语言切换

2、SubRes使用MyRes中的图片资源

在SubRes.Storyboard中,使用MainRes中一张图片arrowblue。


但运行程序后,图片无法显示,原因是ios中,每个不同的framework有着自己默认的bundle,而加载SubRes.Storyboard时,使用的是SubRes的bundle,但是arrowblue存在于MainRes的bundle中,SubRes.Storyboard无法找到这个图片。
此问题的解决办法是在Target->SubRes->BuildPhases->CopyBundleResources中,点击+号,添加MainRes的Assets.xcassets。

这样程序在编译时,会把MainRes的bundle中的资源,复制到SubRes的bundle内,这样程序运行就正常了

3、MyRes.MyLanguage实现

我们期待程序使用用户指定的语言而非系统指定的语言,和前几章相同,我们通过实现MyLanguage类来完成此功能,MyLanguage类中,维护了一个bundleDir,保存当前所有framework的bundle,每次用户切换语言,我们更新bundleDir,构造并且记录用户指定语言的bundle。

public class MyLanguage {
    
    static public let shareInstance = MyLanguage()
    
    private let def = UserDefaults.standard
    
    fileprivate var bundleDir:Dictionary<String,Bundle> = Dictionary<String, Bundle>()
    
    public var dir:Dictionary<String,Bundle>  {
        return bundleDir
    }
    
    let UserLanguage = "UserLanguage"
    let AppleLanguages = "AppleLanguages"
    var language = ""
    
    //用于向MyLanguage类托管一个bundle,维护其语言的设置
    public func registerBundle(_ bundleName:String? = nil) {
        var bb = Bundle.main
        var name = "main"
        if let t = bundleName,let bundle = Bundle.init(identifier: t) {
            bb = bundle
            name = t
        }
        
        
        var string:String = def.value(forKey: UserLanguage) as! String? ?? ""
        if string == "" {
            let languages = def.object(forKey: AppleLanguages) as? NSArray
            if languages?.count != 0 {
                let current = languages?.object(at: 0) as? String
                if current != nil {
                    string = current!
                    def.set(current, forKey: UserLanguage)
                }
            }
        }
        
        string = string.replacingOccurrences(of: "-CN", with: "")
        string = string.replacingOccurrences(of: "-US", with: "")
        
        var path = bb.path(forResource:string , ofType: "lproj")
        if path == nil {
            path = bb.path(forResource:"en" , ofType: "lproj")
        }

        bundleDir[name] = Bundle(path: path!)
        bb.bundleName = name
        
        //类型替换
        object_setClass(bb, BundleEx.self)
    }
    //设置当前语言
    public func setLanguage(language:String) {
        self.language = language
        
        for str in bundleDir.keys {
            var bundle = Bundle.main
            var name = "main"
            if str != "main" {
                bundle = Bundle.init(identifier: str)!
                name = str
            }
            //获取当前语言的bundle
            let path = bundle.path(forResource:language , ofType: "lproj")
            

            bundleDir[name] = Bundle(path: path!)
        }
        
        def.set(language, forKey: UserLanguage)
    }
    
    public func getLanguage()->String {
        return language
    }
    
    private init() {
        
    }
}

同时我们必须重定义Bundle类。

class BundleEx: Bundle {
    //由于storyborad默认的构造流程中,会调用此函数获取storyborad使用的文字信息,所以重写此函数
    //让storyboard去自己定义的包里面取string
    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        //通过MyLanguage获取包
        if let bundle = MyLanguage.shareInstance.bundleDir[bundleName] {
            return bundle.localizedString(forKey: key, value: value, table: tableName)
        } else {
            return super.localizedString(forKey: key, value: value, table: tableName)
        }
    }
}

最后,我们需要扩展Bundle类,记录当前Bundle的名字,方便通过字典查找。

var bundleNameKey = 10000

//为storyboard服务
extension Bundle {
    var bundleName: String {
        set {
            objc_setAssociatedObject(self, &bundleNameKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            if let rs = objc_getAssociatedObject(self, &bundleNameKey) as? String {
                return rs
            }
            return ""
        }
    }
}

在AppDelegate中使用MyLanguage

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
       
    //注册mainbundle
    MyLanguage.shareInstance.registerBundle()
    //注册SubRes的bundle
    //注意包名为Target->General->Bundle Identifier
    MyLanguage.shareInstance.registerBundle("com.luv.SubRes")
    //设置中文
    MyLanguage.shareInstance.setLanguage(language: "zh-Hans")
        
    return true
}

此时,运行程序,可以看到模拟器的语言是英文,但APP中的文字已经成功设置为中文了。

4、SubRes.MyResource实现

在SubRes实现MyResource类,用于获取此framework中的多语言文字和图片,具体内容前几章有提到,就不多做介绍了

class MyResource {
    static public let shareInstance = MyResource()
    
    func GetString(key:String) -> String{
        guard let bundle = MyLanguage.shareInstance.dir["com.luv.SubRes"] else {
            return ""
        }
        return NSLocalizedString(key, tableName: nil, bundle: bundle, comment: "")
        
    }
    
    func GetImage(key:String) ->UIImage? {
        return UIImage.init(named: key, in: MyLanguage.shareInstance.dir["com.luv.SubRes"], compatibleWith: nil)
        
    }
    
    private init() {
        
    }
}
上一篇下一篇

猜你喜欢

热点阅读