随着产品的不断发展,越来越多的公共功能可以封装成组件库,从而被各个模块甚者多个APP而使用,比如字体,间距,头像可以封装UI组件库,
- 利用这些公共功能的组件库,不仅节约节省大量的开发时间,
- 还能减少编译时间,因为如果没有独立的组件库一点点的代码改动都会导致整个App重新编译与连接
公共功能组件库
公共库的使用范围分类
内部库
该库和主项目共享一个Repo
私有库
该库使用独立的私有库Repo
开源库
该库发布到GitHub等开源社区提供给其他开发者使用
封装公共功能组件库
创建
检测
使用
设计UI组件库
间距库
import UIKit
public extension UIFont {
static let designKit = DesignKitTypography()
struct DesignKitTypography {
public var display1: UIFont {
scaled(baseFont: .systemFont(ofSize: 42, weight: .semibold), forTextStyle: .largeTitle, maximumFactor: 1.5)
}
public var display2: UIFont {
scaled(baseFont: .systemFont(ofSize: 36, weight: .semibold), forTextStyle: .largeTitle, maximumFactor: 1.5)
}
public var title1: UIFont {
scaled(baseFont: .systemFont(ofSize: 24, weight: .semibold), forTextStyle: .title1)
}
public var title2: UIFont {
scaled(baseFont: .systemFont(ofSize: 20, weight: .semibold), forTextStyle: .title2)
}
public var title3: UIFont {
scaled(baseFont: .systemFont(ofSize: 18, weight: .semibold), forTextStyle: .title3)
}
public var title4: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .regular), forTextStyle: .headline)
}
public var title5: UIFont {
scaled(baseFont: .systemFont(ofSize: 12, weight: .regular), forTextStyle: .subheadline)
}
public var bodyBold: UIFont {
scaled(baseFont: .systemFont(ofSize: 16, weight: .semibold), forTextStyle: .body)
}
public var body: UIFont {
scaled(baseFont: .systemFont(ofSize: 16, weight: .light), forTextStyle: .body)
}
public var captionBold: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .semibold), forTextStyle: .caption1)
}
public var caption: UIFont {
scaled(baseFont: .systemFont(ofSize: 14, weight: .light), forTextStyle: .caption1)
}
public var small: UIFont {
scaled(baseFont: .systemFont(ofSize: 12, weight: .light), forTextStyle: .footnote)
}
}
}
private extension UIFont.DesignKitTypography {
func scaled(baseFont: UIFont, forTextStyle textStyle: UIFont.TextStyle = .body, maximumFactor: CGFloat? = nil) -> UIFont {
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
if let maximumFactor = maximumFactor {
let maximumPointSize = baseFont.pointSize * maximumFactor
return fontMetrics.scaledFont(for: baseFont, maximumPointSize: maximumPointSize)
}
return fontMetrics.scaledFont(for: baseFont)
}
}
头像组件
import UIKit
public extension UIImageView {
func asAvatar(cornerRadius: CGFloat = 4) {
clipsToBounds = true
layer.cornerRadius = cornerRadius
}
}
点赞按钮
import Foundation
import UIKit
public extension UIButton {
func asStarFavoriteButton(pointSize: CGFloat = 18, weight: UIImage.SymbolWeight = .semibold, scale: UIImage.SymbolScale = .default, fillColor: UIColor = UIColor(hex: 0xf1c40f)) {
let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: pointSize, weight: weight, scale: scale)
let starImage = UIImage(systemName: "star", withConfiguration: symbolConfiguration)
setImage(starImage, for: .normal)
let starFillImage = UIImage(systemName: "star.fill", withConfiguration: symbolConfiguration)
setImage(starFillImage, for: .selected)
tintColor = fillColor
addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
}
func asHeartFavoriteButton(pointSize: CGFloat = 18, weight: UIImage.SymbolWeight = .semibold, scale: UIImage.SymbolScale = .default, fillColor: UIColor = UIColor(hex: 0xe74c3c)) {
let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: pointSize, weight: weight, scale: scale)
let heartImage = UIImage(systemName: "heart", withConfiguration: symbolConfiguration)
setImage(heartImage, for: .normal)
let heartFillImage = UIImage(systemName: "heart.fill", withConfiguration: symbolConfiguration)
setImage(heartFillImage, for: .selected)
tintColor = fillColor
addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
}
}
private extension UIButton {
@objc
private func touchUpInside(sender: UIButton) {
isSelected = !isSelected
}
}
设计功能开关组件
定义了一个协议ToggleType,然后分别定义枚举类型代表三种不同的开关
protocol TogglesDataStoreType {
func isToggleOn(_ toggle: ToggleType) -> Bool
func update(toggle: ToggleType, value: Bool)
}
编译时开关
让编译器通过检查编译条件来启动或者关闭一些功能
import Foundation
enum BuildTargetToggle: ToggleType {
case debug, `internal`, production
}
struct BuildTargetTogglesDataStore: TogglesDataStoreType {
static let shared: BuildTargetTogglesDataStore = .init()
private let buildTarget: BuildTargetToggle
private init() {
#if DEBUG
buildTarget = .debug
#endif
#if INTERNAL
buildTarget = .internal
#endif
#if PRODUCTION
buildTarget = .production
#endif
}
func isToggleOn(_ toggle: ToggleType) -> Bool {
guard let toggle = toggle as? BuildTargetToggle else {
return false
}
return toggle == buildTarget
}
func update(toggle: ToggleType, value: Bool) { }
}
由于不可能在运行时修改编译的条件,因此update方法实现体为空
INTERNAL 要通过xxconfig文件来配置
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) INTERNAL
本地开关
让用户在app里面手动启动或者关闭一些功能
可以让测试人员和产品经理验证
远程开关
让后台关闭一些功能
封装UI组件的时候,遵循的原则
1.尽量使用扩展方法而不是子类来扩展组件,这样做可以使其他开发者在使用组件时,仅需要调用扩展方法,而不必使用特定的类
2.尽量使用代码而不要使用XIb或者Stroryboard,因为有些App完全不使用Interface Builder
3.如果可以,要为组件加上@IBDesignable 和 IBInspecatble支持,这样能够使的开发者在使用Interface Builder 的时候预览我们的组件。
4.尽量使用UIKit,而不要依赖任何第三方库,否则我们可能会引入一个不可控的依赖库
注意点
- 1.组件设计要符合单一功能原则
一个组件只做一件事情,一个组件只做一类相关的事情,每个组件库都要有相对独立且功能单一。
- 分别封装网络库,UI库,蓝牙处理库等底层库,
- 但不能把所有的库合并在一个单独的库里面,这样可以方便上层应用按需使用这些依赖库。
- 2.每次发布新增和更新组件库的时候都需要严格更新版本号
- 3.根据业务需求吧公共模块一点点地移入公共组件库中,一步步的完善组件库的功能