Skip to content

Latest commit

 

History

History
399 lines (308 loc) · 10.7 KB

README-CN.md

File metadata and controls

399 lines (308 loc) · 10.7 KB

JXTheme是一个提供主题属性配置的轻量级基础库。

特性

  • 支持iOS 9+,让你的APP更早的实现DarkMode;
  • 使用theme命名空间属性:view.theme.xx = xx。告别theme_xx属性扩展用法;
  • 使用ThemeProvider传入闭包配置。根据不同的ThemeStyle完成主题属性配置,实现最大化的自定义;
  • ThemeStyle可通过extension自定义style,不再局限于lightdark;
  • 提供customization属性,作为主题切换的回调入口,可以灵活配置任何属性。不再局限于提供的backgroundColortextColor等属性;
  • 支持控件设置overrideThemeStyle,会影响到其子视图;
  • 提供根据ThemeStyle配置属性的常规封装、Plist文件静态加载、服务器动态加载示例;

预览

preview

要求

  • iOS 9.0+
  • XCode 10.2.1+
  • Swift 5.0+

安装

手动

Clone代码,把Sources文件夹拖入项目,就可以使用了;

CocoaPods

target '<Your Target Name>' do
    pod 'JXTheme'
end

先执行pod repo update,再执行pod install

Carthage

在cartfile文件添加:

github "pujiaxin33/JXTheme"

然后执行carthage update --platform iOS ,其他配置请参考Carthage文档

使用示例

扩展ThemeStyle添加自定义style

ThemeStyle内部仅提供了一个默认的unspecifiedstyle,其他的业务style需要自己添加,比如只支持lightdark,代码如下:

extension ThemeStyle {
    static let light = ThemeStyle(rawValue: "light")
    static let dark = ThemeStyle(rawValue: "dark")
}

基础使用

view.theme.backgroundColor = ThemeProvider({ (style) in
    if style == .dark {
        return .white
    }else {
        return .black
    }
})
imageView.theme.image = ThemeProvider({ (style) in
    if style == .dark {
        return UIImage(named: "catWhite")!
    }else {
        return UIImage(named: "catBlack")!
    }
})

自定义属性配置

如果库没有原生支持某个属性,可以在customization里面统一处理。

view.theme.customization = ThemeProvider({[weak self] style in
    //可以选择任一其他属性
    if style == .dark {
        self?.view.bounds = CGRect(x: 0, y: 0, width: 30, height: 30)
    }else {
        self?.view.bounds = CGRect(x: 0, y: 0, width: 80, height: 80)
    }
})

extension ThemeWrapper添加属性

如果某一个属性在项目中经常使用,使用上面的自定义属性配置觉得麻烦,就可以自己extension ThemeWrapper添加想要的属性。(ps:你也可以提交一个Pull Request申请添加哟)

下面是UILabel添加shadowColor的示例:

//自定义添加ThemeProperty,目前仅支持UIView、CALayer、UIBarItem及其它们的子类
extension ThemeWrapper where Base: UILabel {
    var shadowColor: ThemeProvider<UIColor>? {
        set(new) {
            let baseItem = self.base
            ThemeTool.setupViewThemeProperty(view: self.base, key: "UILabel.shadowColor", provider: new) {[weak baseItem] (style) in
                baseItem?.shadowColor = new?.provider(style)
            }
        }
        get { return ThemeTool.getThemeProvider(target: self.base, with: "UILabel.shadowColor") as? ThemeProvider<UIColor> }
    }
}

调用还是一样的:

//自定义属性shadowColor
shadowColorLabel.shadowOffset = CGSize(width: 0, height: 2)
shadowColorLabel.theme.shadowColor = ThemeProvider({ style in
    if style == .dark {
        return .red
    }else {
        return .green
    }
})

配置封装示例

JXTheme是一个提供主题属性配置的轻量级基础库,不限制使用哪种方式加载资源。下面提供的三个示例仅供参考。

ThemeProvider自定义初始化器

比如在项目中添加如下代码:

extension ThemeProvider {
    //根据项目支持的ThemeStyle调整
    init(light: T, dark: T) {
        self.init { style in
            switch style {
            case .light: return light
            case .dark: return dark
            default: return light
            }
        }
    }
}

在业务代码中调用:

tableView.theme.backgroundColor = ThemeProvider(light: UIColor.white, dark: UIColor.white)

这样就可以避免ThemeProvider闭包的形式,更加简洁。

根据枚举定义封装示例

一般的换肤需求,都会有一个UI标准。比如UILabel.textColor定义三个等级,代码如下:

enum TextColorLevel: String {
    case normal
    case mainTitle
    case subTitle
}

然后可以封装一个全局函数传入TextColorLevel返回对应的配置闭包,就可以极大的减少配置时的代码量,全局函数如下:

func dynamicTextColor(_ level: TextColorLevel) -> ThemeProvider<UIColor> {
    switch level {
    case .normal:
        return ThemeProvider({ (style) in
            if style == .dark {
                return UIColor.white
            }else {
                return UIColor.gray
            }
        })
    case .mainTitle:
        ...
    case .subTitle:
        ...
    }
}

主题属性配置时的代码如下:

themeLabel.theme.textColor = dynamicTextColor(.mainTitle)

本地Plist文件配置示例

常规配置封装一样,只是该方法是从本地Plist文件加载配置的具体值,具体代码参加ExampleStaticSourceManager

根据服务器动态添加主题

常规配置封装一样,只是该方法是从服务器加载配置的具体值,具体代码参加ExampleDynamicSourceManager

有状态的控件

某些业务需求会存在一个控件有多种状态,比如选中与未选中。不同的状态对于不同的主题又会有不同的配置。配置代码参考如下:

statusLabel.theme.textColor = ThemeProvider({[weak self] (style) in
    if self?.statusLabelStatus == .isSelected {
        //选中状态一种配置
        if style == .dark {
            return .red
        }else {
            return .green
        }
    }else {
        //未选中状态另一种配置
        if style == .dark {
            return .white
        }else {
            return .black
        }
    }
})

当控件的状态更新时,需要刷新当前的主题属性配置,代码如下:

func statusDidChange() {
    statusLabel.theme.textColor?.refresh()
}

如果你的控件支持多个状态属性,比如有textColorbackgroundColorfont等等,你可以不用一个一个的主题属性调用refresh方法,可以使用下面的代码完成所有配置的主题属性刷新:

func statusDidChange() {
    statusLabel.theme.refresh()
}

overrideThemeStyle

不管主题如何切换,overrideThemeStyleParentView及其子视图的themeStyle都是dark

overrideThemeStyleParentView.theme.overrideThemeStyle = .dark

实现原理

其他说明

为什么使用theme命名空间属性,而不是使用theme_xx扩展属性呢?

  • 如果你给系统的类扩展了N个函数,当你在使用该类时,进行函数索引时,就会有N个扩展的方法干扰你的选择。尤其是你在进行其他业务开发,而不是想配置主题属性时。
  • KingfisherSnapKit等知名三方库,都使用了命名空间属性实现对系统类的扩展,这是一个更Swift的写法,值得学习。

主题切换通知

extension Notification.Name {
    public static let JXThemeDidChange = Notification.Name("com.jiaxin.theme.themeDidChangeNotification")
}

ThemeManager根据用户ID存储主题配置

/// 配置存储的标志key。可以设置为用户的ID,这样在同一个手机,可以分别记录不同用户的配置。需要优先设置该属性再设置其他值。
public var storeConfigsIdentifierKey: String = "default"

迁移到系统API指南

当你的应用最低支持iOS13时,如果需要的话可以按照如下指南,迁移到系统方案。 迁移到系统API指南,点击阅读

目前支持的类及其属性

这里的属性是有继承关系的,比如UIView支持backgroundColor属性,那么它的子类UILabel等也就支持backgroundColor。如果没有你想要支持的类或属性,欢迎提PullRequest进行扩展。

UIView

  • backgroundColor
  • tintColor
  • alpha
  • customization

UILabel

  • font
  • textColor
  • shadowColor
  • highlightedTextColor
  • attributedText

UIButton

  • func setTitleColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)
  • func setTitleShadowColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)
  • func setAttributedTitle(_ textProvider: ThemeAttributedTextDynamicProvider?, for state: UIControl.State)
  • func setImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)
  • func setBackgroundImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)

UITextField

  • font
  • textColor
  • attributedText
  • attributedPlaceholder
  • keyboardAppearance

UITextView

  • font
  • textColor
  • attributedText
  • keyboardAppearance

UIImageView

  • image

CALayer

  • backgroundColor
  • borderColor
  • borderWidth
  • shadowColor
  • customization

CAShapeLayer

  • fillColor
  • strokeColor

UINavigationBar

  • barStyle
  • barTintColor
  • titleTextAttributes
  • largeTitleTextAttributes

UITabBar

  • barStyle
  • barTintColor
  • shadowImage

UISearchBar

  • barStyle
  • barTintColor
  • keyboardAppearance

UIToolbar

  • barStyle
  • barTintColor

UISwitch

  • onTintColor
  • thumbTintColor

UISlider

  • thumbTintColor
  • minimumTrackTintColor
  • maximumTrackTintColor
  • minimumValueImage
  • maximumValueImage

UIRefreshControl

  • attributedTitle

UIProgressView

  • progressTintColor
  • trackTintColor
  • progressImage
  • trackImage

UIPageControl

  • pageIndicatorTintColor
  • currentPageIndicatorTintColor

UIBarItem

  • func setTitleTextAttributes(_ attributesProvider: ThemeAttributesDynamicProvider?, for state: UIControl.State)
  • image

UIBarButtonItem

  • tintColor

UIActivityIndicatorView

  • style
  • color

UIScrollView

  • indicatorStyle

UITableView

  • separatorColor
  • sectionIndexColor
  • sectionIndexBackgroundColor

Contribution

有任何疑问或建议,欢迎提Issue和Pull Request进行交流🤝