Closed cjsliuj closed 7 years ago
有个想法,plist方案无法解决保存非常规类型数据的问题,其实dictionary才是通用方案,plist方案其实可以算作其子集(只接收基础数据值类型的 dictionary),但是做动态下发有点麻烦,需先 archive, 然后下发到app再unarchive,也是可以实现。
Q:ThemeFontPicker
和 ThemeDictionaryPicker
没有 keyPath
的初始化方法:
A:好问题,首先看一个例子(摘自 Demo
Target - AppDelegate.swift
)
let titleAttributes: [[String: AnyObject]] = globalBarTextColors.map { hexString in
return [
NSForegroundColorAttributeName: UIColor(rgba: hexString),
NSFontAttributeName: UIFont.systemFont(ofSize: 16),
NSShadowAttributeName: shadow
]
}
navigationBar.theme_titleTextAttributes = ThemeDictionaryPicker.pickerWithDicts(titleAttributes)
这是 ThemeDictionaryPicker
使用集合初始化的一个使用场景,创建一个颜色不同的字典集合,然后转换为 ThemeDictionaryPicker
后设置到导航栏的 theme_titleTextAttributes
属性中。
然而如果通过 plist
设置的话,颜色、字体、阴影都无法实例化,因为无法知道字典中某个 key
对应的是什么类型。
如果要让 SwiftTheme
支持连字典里面的内容都可以实例化,使用方式上的改动是不可避免的:
第一步:使用特定 key
的名称,例如是 NSForegroundColorAttributeName
就转为 UIColor
。
第二步:使用特定格式的 value
,例如 font: PingFangSC-Regular 16
。
或者使用嵌套,如果值是 font
则使用字典,里面再包含 name
、size
等。
然后总结一下我看到的问题:
第一步:
key
的名称长,如果不是复制过来的,没有自动补全很容易出错。
强迫用户使用特定的 key
,与其它自定义的 key
使用方法不统一。
第二步:
如果使用特定格式,类似于 UIFont
,是否每一种类型都要定义一种不同的格式?而且识别与提取真正的值会影响运行速度(可能影响很小)。
如果使用嵌套,虽然可以在类型中包含每个属性的值,但是每种类型的初始化方法都是不同的,如果必要的初始化参数没有,还是会增加调试的负担。
所以基于以上的原因和疑问,ThemeDictionaryPicker
现在暂时不支持通过 keyPath
初始化,ThemeFontPicker
的原因是类似的,需要统一解决方案才能做。
所以目前通过 plist
实现上面的例子大概是这样的:
// ...
NotificationCenter.default.addObserver(self, selector: #selector(themeUpdate), name: NSNotification.Name(rawValue: ThemeUpdateNotification), object: nil)
}
func themeUpdate() {
let dict = ThemeManager.dictionaryForKeyPath("someKeyPath")! // 为了方便演示所以强制解包
let titleAttributes: [String: AnyObject] = [
NSForegroundColorAttributeName: UIColor(rgba: dict["color"] as! String),
NSFontAttributeName: UIFont.systemFont(ofSize: dict["fontSize"] as! CGFloat)
]
navigationBar.titleTextAttributes = titleAttributes
}
注册通知这里体验不是很好,之后可能会做一些改进,让使用者只关心转换的过程。 大概就是这样,这个问题当时还是比较纠结的,等于两害相权取其轻,期待你的回复。
Q:Dictionary
比 plist
更通用,plist
应该作为子集。
A:其实现在就是这样的,在 ThemeManager.swift
你可以看到 public class func setTheme(plistName: String, path: ThemePath)
和 public class func setTheme(dict: NSDictionary, path: ThemePath)
方法,实际上前者只是从 plist
中取出 Dictionary
然后调用了后者。
谢谢上面的回答。 刚又遇到了个问题。。不太好扩展啊 我想给 UIBarItem 扩展 theme_image 和 theme_selectedImage 结果发现 UIKit+Theme.swift 中,getThemePicker 和 setThemePicker 是 私有的 ,这条路不通,继续往下,发现 themePickers 是 internal 的。。。这可咋弄
对了还有个问题,picker 把值检索出来后,最后给个回调让外界有机会处理一下, e.g. vc.tabBarItem.image = UIImage.init(named: "xxx")?.withRenderingMode(.alwaysOriginal) 这种场景 theme_image 就没法处理了 个人想法:目前内部已经保存了'属性设置器(setImage/setText/xxxxx)'、'值检索器(各种picker)',再添加一个'值处理器'即可
你可以 fork 一下项目,并在 UIKit+Theme.swift
中增加 UIBarItem
的 extension
。
然后在 CocoaPods
中使用你 fork 的项目,例如:
pod 'SwiftTheme', :git => 'https://github.com/cjsliujie/SwiftTheme.git'
如果使用没有问题,欢迎提个 Pull Request
plist
因为是设置 keyPath
,而不像设置数组那样可以接触到值,没有办法对值进行处理,这个确实可以增加设置的方法,你说的值处理器是如何实现的,可以详细说一下,我现在还没有什么 idea
顺便提一下:如果我没记错的话 tabBarItem
动态改变 image
是不会在界面上生效的,相关的 #26
谢谢上面关于 tabBarItem 的提醒,节省我不少调试时间
关于 '值处理器',大概想法是:
外界如何设置:创建picker的时候可以同时选择传入一个闭包,其中包含了值处理逻辑
内部如何保存:与 themePickers 类似,可以再搞个字典 ,key 仍然是 selector,value 是 '值处理器'
执行时机:performThemePicker 中 ,真正的 perform 之前
上面是刚开始下意识的想法,后来转念一想,更加通用的模型是: 内部更新属性前调用外部设置的回调,让外部有机会处理 内部更新完属性后调用外部设置的回调,让外部有机会处理
这样可以解决 如你上面说的 tabBarItem 的问题。且相比通知,代码逻辑更加紧凑。
总结下就是:目前 SwiftTheme 完全托管了属性的设置与属性值的检索,但是最好能够提供一种方式,让外部有机会介入这个过程,这样会更灵活。(突然想到:这样的话,外部使用者可以自定义非基本数据类型的 string形式 的字面量表达式,picker检索出来后 ,外部自己去转换成合适的值)
大概明白你的意思,是不是类似于:
imageView.theme_image = ThemeImagePicker(keyPath: "someKeyPath") { stringValue in
return UIImage(urlString: stringValue)!.withRenderingMode(.alwaysOriginal)
}
imageView.theme_image = ThemeImagePicker(keyPath: "someKeyPath").map { imageValue in
return imageValue.withRenderingMode(.alwaysOriginal)
}
恩
在版本 0.3.3 中增加了提供外部处理的初始化和类方法,增加了一个 map
参数,可以传入一个处理函数。
下面是一个例子,设置导航栏的标题文字属性:
navigationBar.theme_titleTextAttributes = ThemeDictionaryPicker(keyPath: "Global.barTextColor", map: { value in
guard
let rgba = value as? String,
let color = try? UIColor(rgba_throws: rgba) else {
return nil
}
let shadow = NSShadow(); shadow.shadowOffset = CGSize.zero
let titleTextAttributes = [
NSForegroundColorAttributeName: color,
NSFontAttributeName: UIFont.systemFont(ofSize: 16),
NSShadowAttributeName: shadow
]
return titleTextAttributes
})
在之前这只能通过注册一个主题切换的通知来实现,现在可以灵活的进行转换,然后由 SwiftTheme
继续做主题切换的工作。
也就是说,如果传入 map
函数,keyPath
的值可以是任意类型。所以 map
给你的参数永远是一个 Any?
类型,你知道 keyPath
究竟对应的是什么类型(字符串字典数组或是别的什么),处理并最后返回 ThemeDictionaryPicker
需要的字典即可。
另外,所有 ThemePicker
的子类现在都拥有这个方法,感谢 @cjsliujie 提供的 idea !
看了下代码,发现 ThemeFontPicker 和 ThemeDictionaryPicker 没有以keyPath为参数的初始化方法,ThemeFontPicker 没有比较好好理解,字符串确实不太好表达 Font,ThemeDictionaryPicker 没有就比较奇怪了(plist中是可以表达dictionary的呀),而且 ThemeManager+Plist 中也定义了 dictionaryForKeyPath(_),可能是我还没太理解,希望能够解释以下,谢谢~。
另外,上述两者如果没有 keypath初始化方法,那么使用的时候就会和其他picker不一致了,其他的picker只要指定 keypath,就可以自动完成根据theme切换属性值的功能,而如果是上述的两个picker的话,恐怕要外界自行根据theme去init?