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

开发语言: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的项目。

  • FrameworkTest为主项目,包含Main.Storyboard,放置了一个文字用于测试多语言,同时点击此文字,会加载其他framework中的storyboard
  • MainRes作为所有项目都要使用的framework,通过MyLanguage维护所有项目的语言设置,并且包含了一些其他项目可复用的资源文件。
  • SubRes作为一个独立的项目,包含SubRes.Storyboard,并且使用MainRes中的资源,同时通过MyResource来读取此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() {
        
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350