{"id":18974662,"url":"https://github.com/pujiaxin33/jxtheme","last_synced_at":"2025-10-15T02:08:06.675Z","repository":{"id":56916739,"uuid":"196173542","full_name":"pujiaxin33/JXTheme","owner":"pujiaxin33","description":"A powerful and lightweight and customization theme/skin library for iOS 9+ in swift. 主题、换肤、暗黑模式","archived":false,"fork":false,"pushed_at":"2021-08-27T02:01:41.000Z","size":511,"stargazers_count":262,"open_issues_count":0,"forks_count":36,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-15T08:32:47.762Z","etag":null,"topics":["dark","dark-mode","dark-theme","darkmode","ios9","night","night-mode","nightmode","nightwatch","skin","swift","theme"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pujiaxin33.png","metadata":{"files":{"readme":"README-CN.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-10T09:16:02.000Z","updated_at":"2025-09-12T07:32:07.000Z","dependencies_parsed_at":"2022-08-20T21:20:23.028Z","dependency_job_id":null,"html_url":"https://github.com/pujiaxin33/JXTheme","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/pujiaxin33/JXTheme","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXTheme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXTheme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXTheme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXTheme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pujiaxin33","download_url":"https://codeload.github.com/pujiaxin33/JXTheme/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXTheme/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279033978,"owners_count":26089402,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-15T02:00:07.814Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dark","dark-mode","dark-theme","darkmode","ios9","night","night-mode","nightmode","nightwatch","skin","swift","theme"],"created_at":"2024-11-08T15:15:43.615Z","updated_at":"2025-10-15T02:08:06.628Z","avatar_url":"https://github.com/pujiaxin33.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cdiv align=center\u003e\u003cimg width=\"468\" height=\"90\" src=\"https://github.com/pujiaxin33/JXTheme/blob/master/GIF/JXTheme.png\"/\u003e\u003c/div\u003e\n\nJXTheme是一个提供主题属性配置的轻量级基础库。\n\n\n# 特性\n\n- [x] 支持iOS 9+，让你的APP更早的实现`DarkMode`;\n- [x] 使用`theme`命名空间属性:`view.theme.xx = xx`。告别`theme_xx`属性扩展用法；\n- [x] 使用`ThemeProvider`传入闭包配置。根据不同的`ThemeStyle`完成主题属性配置，实现最大化的自定义；\n- [x] `ThemeStyle`可通过`extension`自定义style，不再局限于`light`和`dark`;\n- [x] 提供`customization`属性，作为主题切换的回调入口，可以灵活配置任何属性。不再局限于提供的`backgroundColor`、`textColor`等属性；\n- [x] 支持控件设置`overrideThemeStyle`，会影响到其子视图； \n- [x] 提供根据`ThemeStyle`配置属性的常规封装、Plist文件静态加载、服务器动态加载示例；\n\n# 预览\n![preview](https://github.com/pujiaxin33/JXTheme/blob/master/GIF/preview.gif)\n\n# 要求\n\n- iOS 9.0+\n- XCode 10.2.1+\n- Swift 5.0+\n\n# 安装\n\n## 手动\n\nClone代码，把Sources文件夹拖入项目，就可以使用了；\n\n## CocoaPods\n\n```ruby\ntarget '\u003cYour Target Name\u003e' do\n    pod 'JXTheme'\nend\n```\n先执行`pod repo update`，再执行`pod install`\n\n## Carthage\n在cartfile文件添加：\n```\ngithub \"pujiaxin33/JXTheme\"\n```\n然后执行`carthage update --platform iOS` ，其他配置请参考Carthage文档\n\n# 使用示例\n\n## 扩展`ThemeStyle`添加自定义style\n`ThemeStyle`内部仅提供了一个默认的`unspecified`style，其他的业务style需要自己添加，比如只支持`light`和`dark`，代码如下：\n```Swift\nextension ThemeStyle {\n    static let light = ThemeStyle(rawValue: \"light\")\n    static let dark = ThemeStyle(rawValue: \"dark\")\n}\n```\n\n## 基础使用\n```Swift\nview.theme.backgroundColor = ThemeProvider({ (style) in\n    if style == .dark {\n        return .white\n    }else {\n        return .black\n    }\n})\nimageView.theme.image = ThemeProvider({ (style) in\n    if style == .dark {\n        return UIImage(named: \"catWhite\")!\n    }else {\n        return UIImage(named: \"catBlack\")!\n    }\n})\n```\n\n## 自定义属性配置\n如果库没有原生支持某个属性，可以在customization里面统一处理。\n```Swift\nview.theme.customization = ThemeProvider({[weak self] style in\n    //可以选择任一其他属性\n    if style == .dark {\n        self?.view.bounds = CGRect(x: 0, y: 0, width: 30, height: 30)\n    }else {\n        self?.view.bounds = CGRect(x: 0, y: 0, width: 80, height: 80)\n    }\n})\n```\n\n## extension ThemeWrapper添加属性\n\n如果某一个属性在项目中经常使用，使用上面的**自定义属性配置**觉得麻烦，就可以自己extension ThemeWrapper添加想要的属性。（ps：你也可以提交一个Pull Request申请添加哟）\n\n下面是UILabel添加shadowColor的示例：\n```Swift\n//自定义添加ThemeProperty，目前仅支持UIView、CALayer、UIBarItem及其它们的子类\nextension ThemeWrapper where Base: UILabel {\n    var shadowColor: ThemeProvider\u003cUIColor\u003e? {\n        set(new) {\n            let baseItem = self.base\n            ThemeTool.setupViewThemeProperty(view: self.base, key: \"UILabel.shadowColor\", provider: new) {[weak baseItem] (style) in\n                baseItem?.shadowColor = new?.provider(style)\n            }\n        }\n        get { return ThemeTool.getThemeProvider(target: self.base, with: \"UILabel.shadowColor\") as? ThemeProvider\u003cUIColor\u003e }\n    }\n}\n```\n调用还是一样的：\n```Swift\n//自定义属性shadowColor\nshadowColorLabel.shadowOffset = CGSize(width: 0, height: 2)\nshadowColorLabel.theme.shadowColor = ThemeProvider({ style in\n    if style == .dark {\n        return .red\n    }else {\n        return .green\n    }\n})\n```\n\n## 配置封装示例\n`JXTheme`是一个提供主题属性配置的轻量级基础库，不限制使用哪种方式加载资源。下面提供的三个示例仅供参考。\n\n### ThemeProvider自定义初始化器\n比如在项目中添加如下代码：\n```Swift\nextension ThemeProvider {\n    //根据项目支持的ThemeStyle调整\n    init(light: T, dark: T) {\n        self.init { style in\n            switch style {\n            case .light: return light\n            case .dark: return dark\n            default: return light\n            }\n        }\n    }\n}\n```\n在业务代码中调用：\n```Swift\ntableView.theme.backgroundColor = ThemeProvider(light: UIColor.white, dark: UIColor.white)\n```\n这样就可以避免ThemeProvider闭包的形式，更加简洁。\n\n### 根据枚举定义封装示例\n\n一般的换肤需求，都会有一个UI标准。比如`UILabel.textColor`定义三个等级，代码如下：\n```Swift\nenum TextColorLevel: String {\n    case normal\n    case mainTitle\n    case subTitle\n}\n```\n然后可以封装一个全局函数传入`TextColorLevel`返回对应的配置闭包，就可以极大的减少配置时的代码量，全局函数如下：\n```Swift\nfunc dynamicTextColor(_ level: TextColorLevel) -\u003e ThemeProvider\u003cUIColor\u003e {\n    switch level {\n    case .normal:\n        return ThemeProvider({ (style) in\n            if style == .dark {\n                return UIColor.white\n            }else {\n                return UIColor.gray\n            }\n        })\n    case .mainTitle:\n        ...\n    case .subTitle:\n        ...\n    }\n}\n```\n主题属性配置时的代码如下：\n```Swift\nthemeLabel.theme.textColor = dynamicTextColor(.mainTitle)\n```\n\n### 本地Plist文件配置示例\n与**常规配置封装**一样，只是该方法是从本地Plist文件加载配置的具体值，具体代码参加`Example`的`StaticSourceManager`类\n\n### 根据服务器动态添加主题\n与**常规配置封装**一样，只是该方法是从服务器加载配置的具体值，具体代码参加`Example`的`DynamicSourceManager`类\n\n## 有状态的控件\n某些业务需求会存在一个控件有多种状态，比如选中与未选中。不同的状态对于不同的主题又会有不同的配置。配置代码参考如下：\n```Swift\nstatusLabel.theme.textColor = ThemeProvider({[weak self] (style) in\n    if self?.statusLabelStatus == .isSelected {\n        //选中状态一种配置\n        if style == .dark {\n            return .red\n        }else {\n            return .green\n        }\n    }else {\n        //未选中状态另一种配置\n        if style == .dark {\n            return .white\n        }else {\n            return .black\n        }\n    }\n})\n```\n\n当控件的状态更新时，需要刷新当前的主题属性配置，代码如下：\n```Swift\nfunc statusDidChange() {\n    statusLabel.theme.textColor?.refresh()\n}\n```\n\n如果你的控件支持多个状态属性，比如有`textColor`、`backgroundColor`、`font`等等，你可以不用一个一个的主题属性调用`refresh`方法，可以使用下面的代码完成所有配置的主题属性刷新：\n```Swift\nfunc statusDidChange() {\n    statusLabel.theme.refresh()\n}\n```\n\n## overrideThemeStyle\n不管主题如何切换，`overrideThemeStyleParentView`及其子视图的`themeStyle`都是`dark`\n```Swift \noverrideThemeStyleParentView.theme.overrideThemeStyle = .dark\n```\n\n# 实现原理\n\n- [实现原理](https://github.com/pujiaxin33/JXTheme/blob/master/Document/%E5%8E%9F%E7%90%86.md)\n\n\n# 其他说明\n\n## 为什么使用`theme`命名空间属性，而不是使用`theme_xx`扩展属性呢？\n- 如果你给系统的类扩展了N个函数，当你在使用该类时，进行函数索引时，就会有N个扩展的方法干扰你的选择。尤其是你在进行其他业务开发，而不是想配置主题属性时。\n- 像`Kingfisher`、`SnapKit`等知名三方库，都使用了命名空间属性实现对系统类的扩展，这是一个更`Swift`的写法，值得学习。\n\n## 主题切换通知\n```Swift\nextension Notification.Name {\n    public static let JXThemeDidChange = Notification.Name(\"com.jiaxin.theme.themeDidChangeNotification\")\n}\n```\n\n## `ThemeManager`根据用户ID存储主题配置\n\n```\n/// 配置存储的标志key。可以设置为用户的ID，这样在同一个手机，可以分别记录不同用户的配置。需要优先设置该属性再设置其他值。\npublic var storeConfigsIdentifierKey: String = \"default\"\n```\n\n## 迁移到系统API指南\n当你的应用最低支持iOS13时，如果需要的话可以按照如下指南，迁移到系统方案。\n[迁移到系统API指南，点击阅读](https://github.com/pujiaxin33/JXTheme/blob/master/Document/%E8%BF%81%E7%A7%BB%E5%88%B0%E7%B3%BB%E7%BB%9FAPI%E6%8C%87%E5%8D%97.md)\n\n# 目前支持的类及其属性\n\n这里的属性是有继承关系的，比如`UIView`支持`backgroundColor`属性，那么它的子类`UILabel`等也就支持`backgroundColor`。如果没有你想要支持的类或属性，欢迎提PullRequest进行扩展。\n\n## UIView\n\n- `backgroundColor`\n- `tintColor`\n- `alpha`\n- `customization`\n\n## UILabel\n\n- `font`\n- `textColor`\n- `shadowColor`\n- `highlightedTextColor`\n- `attributedText`\n\n## UIButton\n\n- `func setTitleColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)`\n- `func setTitleShadowColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)`\n- `func setAttributedTitle(_ textProvider: ThemeAttributedTextDynamicProvider?, for state: UIControl.State)`\n- `func setImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)`\n- `func setBackgroundImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)`\n\n## UITextField\n\n- `font`\n- `textColor`\n- `attributedText`\n- `attributedPlaceholder`\n- `keyboardAppearance`\n\n## UITextView\n\n- `font`\n- `textColor`\n- `attributedText`\n- `keyboardAppearance`\n\n## UIImageView\n\n- `image`\n\n## CALayer\n\n- `backgroundColor`\n- `borderColor`\n- `borderWidth`\n- `shadowColor`\n- `customization`\n\n## CAShapeLayer\n\n- `fillColor`\n- `strokeColor`\n\n## UINavigationBar\n\n- `barStyle`\n- `barTintColor`\n- `titleTextAttributes`\n- `largeTitleTextAttributes`\n\n## UITabBar\n\n- `barStyle`\n- `barTintColor`\n- `shadowImage`\n\n## UISearchBar\n\n- `barStyle`\n- `barTintColor`\n- `keyboardAppearance`\n\n## UIToolbar\n\n- `barStyle`\n- `barTintColor`\n\n## UISwitch\n\n- `onTintColor`\n- `thumbTintColor`\n\n## UISlider\n\n- `thumbTintColor`\n- `minimumTrackTintColor`\n- `maximumTrackTintColor`\n- `minimumValueImage`\n- `maximumValueImage`\n\n## UIRefreshControl\n\n- `attributedTitle`\n\n## UIProgressView\n\n- `progressTintColor`\n- `trackTintColor`\n- `progressImage`\n- `trackImage`\n\n## UIPageControl\n\n- `pageIndicatorTintColor`\n- `currentPageIndicatorTintColor`\n\n## UIBarItem\n\n- `func setTitleTextAttributes(_ attributesProvider: ThemeAttributesDynamicProvider?, for state: UIControl.State)`\n- `image`\n\n## UIBarButtonItem\n\n- `tintColor`\n\n## UIActivityIndicatorView\n\n- `style`\n- `color`\n\n## UIScrollView\n\n- `indicatorStyle`\n\n## UITableView\n\n- `separatorColor`\n- `sectionIndexColor`\n- `sectionIndexBackgroundColor`\n\n# Contribution\n\n有任何疑问或建议，欢迎提Issue和Pull Request进行交流🤝\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpujiaxin33%2Fjxtheme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpujiaxin33%2Fjxtheme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpujiaxin33%2Fjxtheme/lists"}