开发语言: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 = Dictionary()
public var dir:Dictionary {
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() {
}
}