本文首发于 Ficow Shen's Blog,原文地址: iOS 高效灵活地配置可复用视图组件的主题

 

内容概览

  • 前言
  • 如何配置主题?
  • 如何更高效地配置主题?
  • 面向协议/接口的方案

 

 

前言

 

在开发可视化应用的过程中,配置控件的样式是最常见的工作内容。请问读者是否遇到过这样的需求:在多个项目中复用多种可视化控件,而且这些控件可以配置颜色、字体等可视化元素?

本文主要针对控件数量较大,而且需要配置的控件属性较多的这种需求对主题配置方案进行探索,希望能够给读者带来些许启发。

 

 

如何配置主题?

 

大家最熟悉的方式就是给控件添加 控制样式的属性,然后 让调用方去设置控件的样式属性 以实现自定义样式的需求。

public final class ReusableComponent: UIView {
private let titleLabel = UILabel() // 暴露一个颜色配置属性,供调用方更改文本颜色
public var titleColor: UIColor = .darkGray {
didSet {
titleLabel.textColor = titleColor
}
}
} let component = ReusableComponent()
component.titleColor = .red

在控件数量较少、样式属性也较少的情况下,直接设置样式属性的方式是非常简单高效的。

 

如果控件数量大样式属性较多使用范围广甚至需要在多个项目中使用时,如何实现简单高效的样式配置呢?请看以下示例代码,并思考这个问题。

public final class ReusableComponent: UIView {
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let confirmButton = UIButton() public var titleColor: UIColor = .darkGray {
didSet {
titleLabel.textColor = titleColor
}
} public var titleFont: UIFont = .systemFont(ofSize: 20) {
didSet {
titleLabel.font = titleFont
}
} public var descriptionColor: UIColor = .gray {
didSet {
descriptionLabel.textColor = descriptionColor
}
} public var descriptionFont: UIFont = .systemFont(ofSize: 14) {
didSet {
descriptionLabel.font = descriptionFont
}
} public var confirmTitleColor: UIColor = .darkGray {
didSet {
confirmButton.setTitleColor(confirmTitleColor, for: .normal)
}
} public var confirmTitleFont: UIFont = .systemFont(ofSize: 16) {
didSet {
confirmButton.titleLabel?.font = confirmTitleFont
}
}
} let component = ReusableComponent()
component.titleColor = .black
component.titleFont = .systemFont(ofSize: 19)
component.descriptionColor = .lightGray
component.descriptionFont = .systemFont(ofSize: 13)
component.confirmTitleColor = .black
component.confirmTitleFont = .systemFont(ofSize: 15)

请看上面的示例代码,这里仅仅配置几个样式属性就已经需要写很多行代码。如果需要大面积修改这种配置,我们很容易就漏掉某个属性。怎么办?

 

 

 

public final class ReusableComponent: UIView {

    public struct Theme {
let titleColor: UIColor
let titleFont: UIFont
let descriptionColor: UIColor
let descriptionFont: UIFont
let confirmTitleColor: UIColor
let confirmTitleFont: UIFont
} public static let defaultTheme = Theme(titleColor: .darkGray,
titleFont: .systemFont(ofSize: 20),
descriptionColor: .gray,
descriptionFont: .systemFont(ofSize: 14),
confirmTitleColor: .darkGray,
confirmTitleFont: .systemFont(ofSize: 16)) public var theme: Theme = defaultTheme {
didSet {
titleLabel.textColor = theme.titleColor
titleLabel.font = theme.titleFont
descriptionLabel.textColor = theme.descriptionColor
descriptionLabel.font = theme.descriptionFont
confirmButton.setTitleColor(theme.confirmTitleColor, for: .normal)
confirmButton.titleLabel?.font = theme.confirmTitleFont
}
} private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let confirmButton = UIButton()
} let component = ReusableComponent()
let theme = ReusableComponent.Theme(titleColor: .black,
titleFont: .systemFont(ofSize: 19),
descriptionColor: .lightGray,
descriptionFont: .systemFont(ofSize: 13),
confirmTitleColor: .black,
confirmTitleFont: .systemFont(ofSize: 15))
component.theme = theme

为控件定义一个主题类型并定义一个主题属性,调用方不用担心漏掉某个配置项。而且,调用方甚至可以定义一个全局的主题对象,在需要使用的时候直接赋值即可。

但是,我们依然要为每一个控件实例进行样式配置。您可以设想一下,如果您需要对 ReusableComponent1, ReusableComponent2, ... , ReusableComponentN 这些控件进行主题配置,您就需要定义超级多的主题类型。 而且,调用方需要确切知晓控件里面的主题类型,然后在配置主题的时候去初始化一个主题类型的实例并传给控件实例。

那么,有没有什么办法更简单、灵活、高效呢?

 

 

如何更高效地配置主题?

 

每次用到控件都去指定主题的方式极其低效,我们先要想方设法优化这个问题。How?

public final class ReusableComponent: UIView {

    // ...

    public static var theme: Theme = defaultTheme

    public var theme: Theme = ReusableComponent.theme {
didSet {
titleLabel.textColor = theme.titleColor
titleLabel.font = theme.titleFont
descriptionLabel.textColor = theme.descriptionColor
descriptionLabel.font = theme.descriptionFont
confirmButton.setTitleColor(theme.confirmTitleColor, for: .normal)
confirmButton.titleLabel?.font = theme.confirmTitleFont
}
} // ...
} ReusableComponent.theme = ReusableComponent.Theme(titleColor: .black,
titleFont: .systemFont(ofSize: 19),
descriptionColor: .lightGray,
descriptionFont: .systemFont(ofSize: 13),
confirmTitleColor: .black,
confirmTitleFont: .systemFont(ofSize: 15))
let component = ReusableComponent()
print(component.theme)

一般来说,应用内使用的控件的主题风格都是统一的。所以,更多的实际场景是我们需要对控件类型进行统一的样式配置。

ReusableComponent类型上增加一个静态变量,这样只需要在使用控件前,对控件进行统一配置即可。如果稍后需要对某个控件实例进行定制,只需要修改控件实例的theme属性即可。这解决了配置效率低下的问题。

如果控件是定义在一个公用库里面,有多个项目需要用到库中的控件,那么直接暴露控件内部定义的主题类型给调用方将是一件非常不妙的事情。我们应该尽可能少地暴露公用库中的内容,以达到高度的封装效果。这样,以后可能会发生的内部变动就不担心会受到下游调用方的约束。

那么,怎么封装呢?

 

 

面向协议/接口的方案

 

如果您长期使用Swift开发语言,面向协议编程的概念您一定听说过。灵魂拷问又来了,究竟怎样的编程方式才是面向协议编程呢?

public protocol ReusableComponentTheme {
var titleColor: UIColor { get }
var titleFont: UIFont { get }
var descriptionColor: UIColor { get }
var descriptionFont: UIFont { get }
var confirmTitleColor: UIColor { get }
var confirmTitleFont: UIFont { get }
} public final class ReusableComponent: UIView { struct Theme: ReusableComponentTheme {
var titleColor: UIColor { .darkGray }
var titleFont: UIFont { .systemFont(ofSize: 20) }
var descriptionColor: UIColor { .gray }
var descriptionFont: UIFont { .systemFont(ofSize: 14) }
var confirmTitleColor: UIColor { .darkGray }
var confirmTitleFont: UIFont { .systemFont(ofSize: 16) }
} public static var theme: ReusableComponentTheme = Theme() public var theme: ReusableComponentTheme = ReusableComponent.theme {
didSet {
titleLabel.textColor = theme.titleColor
titleLabel.font = theme.titleFont
descriptionLabel.textColor = theme.descriptionColor
descriptionLabel.font = theme.descriptionFont
confirmButton.setTitleColor(theme.confirmTitleColor, for: .normal)
confirmButton.titleLabel?.font = theme.confirmTitleFont
}
} private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let confirmButton = UIButton()
} struct CustomReusableComponentTheme: ReusableComponentTheme {
var titleColor: UIColor { .black }
var titleFont: UIFont { .systemFont(ofSize: 19) }
var descriptionColor: UIColor { .lightGray }
var descriptionFont: UIFont { .systemFont(ofSize: 13) }
var confirmTitleColor: UIColor { .black }
var confirmTitleFont: UIFont { .systemFont(ofSize: 15) }
} ReusableComponent.theme = CustomReusableComponentTheme()
let component = ReusableComponent()
print(component.theme)

针对控件的主题定义一个协议,然后让主题类型去遵循这个协议。调用方不再知晓控件内部的主题类型,控件内部后续的变动不会导致调用方的编译错误,这样也就实现了调用链上下游的解耦。

如果以后需要对控件内部的样式进行调整,您可以定义新的协议来满足新的需求,而不是去修改旧的协议。这种变更方式与后端接口支持不同版本类似,也比较灵活。

 

以上就是本文的全部内容,希望对您有所启发!

 

iOS 高效灵活地配置可复用视图组件的主题的更多相关文章

  1. APPKIT打造稳定、灵活、高效的运营配置平台

    一.背景 美团App.大众点评App都是重运营的应用.对于App里运营资源.基础配置,需要根据城市.版本.平台.渠道等不同的维度进行运营管理.如何在版本快速迭代过程中,保持运营资源能够被高效.稳定和灵 ...

  2. iOS高效调试

    写代码难免出现bug. 储备些调试技能绝对能够提高你的工作效率,让bug无所遁形.下面就和大家分享一些我在工作中常用的iOS调试小技能. 1. 打印 最简单,基础的调试方法就是打印日志了.贴出两段封装 ...

  3. SAP CRM 复用视图

    在设计任何视图或组件的时候,我们需要以可复用的方式来设计它.UI组件设计的主要目标即可复用. 例如:几乎每个事务都要处理合作伙伴(客户).如果我们想要在Web UI显示那些合作伙伴,需要设计一个视图. ...

  4. Box(视图组件)如何在多个页面不同视觉规范下的复用

    本文来自 网易云社区 . 问题描述 Android App中的页面元素,都是由一个个Box(可以理解成一个个自定义View组件和Widget同级)组成,这些Box可以在不同的页面.不同的模块达到复用的 ...

  5. 百度DMLC分布式深度机器学习开源项目(简称“深盟”)上线了如xgboost(速度快效果好的Boosting模型)、CXXNET(极致的C++深度学习库)、Minerva(高效灵活的并行深度学习引擎)以及Parameter Server(一小时训练600T数据)等产品,在语音识别、OCR识别、人脸识别以及计算效率提升上发布了多个成熟产品。

    百度为何开源深度机器学习平台?   有一系列领先优势的百度却选择开源其深度机器学习平台,为何交底自己的核心技术?深思之下,却是在面对业界无奈时的远见之举.   5月20日,百度在github上开源了其 ...

  6. 一些iOS高效开源类库

    因为iOS SDK相对比较底层,所以开发者就得受累多做一些体力活.不过幸运的是,有很多第三方的类库可以用来简化很多不必要的工作.笔者整理了一下在本人学习过程中用到的一些比较有用Objective-C开 ...

  7. 灵活QinQ配置

    华为交换机灵活QinQ配置列子 配置vlan2 为内层vlan vlan100 为外层vlan #用户端 Gi // qinq vlan-translation enable port hybrid ...

  8. iOS开发:使用Tab Bar切换视图

    iOS开发:使用Tab Bar切换视图 上一篇文章提到了多视图程序中各个视图之间的切换,用的Tool Bar,说白了还是根据触发事件使用代码改变Root View Controller中的Conten ...

  9. Xamarin iOS教程之进度条和滚动视图

    Xamarin iOS教程之进度条和滚动视图 Xamarin iOS 进度条 进度条可以看到每一项任务现在的状态.例如在下载的应用程序中有进度条,用户可以很方便的看到当前程序下载了多少,还剩下多少.Q ...

随机推荐

  1. 一.1搭建跨平台的统一python开发环境

    搭建跨平台的统一python开发环境: 使用开发环境的好处: 可不用在服务器上直接修改源代码---写的代码首先得入版本库(放git或giitlab中),在本地写代码提交到git中.然后在服务器上git ...

  2. 真的可以,用C语言实现面向对象编程OOP

    ID:技术让梦想更伟大 作者:李肖遥 解释区分一下C语言和OOP 我们经常说C语言是面向过程的,而C++是面向对象的,然而何为面向对象,什么又是面向过程呢?不管怎么样,我们最原始的目标只有一个就是实现 ...

  3. 前端走进机器学习生态,在 Node.js 中使用 Python

    这次给大家带来一个好东西,它的主要用途就是能让大家在 Node.js 中使用 Python 的接口和函数.可能你看到这里会好奇,会疑惑,会不解,我 Node.js 大法那么好,干嘛要用 Python ...

  4. 基于git的博客(含站点与小程序)

    1 效果 静态站点: blog.makergyt.com 备用链接: github.blog.makergyt.com 小程序: 语雀:<MakerGYT blog> 2 需求分析 2.1 ...

  5. 每日一题 - 剑指 Offer 45. 把数组排成最小的数

    题目信息 时间: 2019-07-01 题目链接:Leetcode tag: 快速排序 难易程度:中等 题目描述: 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最 ...

  6. 特殊方格棋盘【状压DP】

    特殊方格棋盘[状压DP] 讲真状压DP这个东西只不过是有那么亿丢丢考验心态罢了(确信) 先从板子题说起,另加一些基础知识 题目描述 在的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方 ...

  7. 整理一下CSS最容易躺枪的二十规则,大家能躺中几条?

    整理一下CSS最容易躺枪的二十规则,大家能躺中几条? 转载:API中文网 一.float:left/right 或者 position: absolute 后还写上 display:block? 二. ...

  8. 1.Unity3d的新建场景和保存场景

    Unit3d开发游戏需要使用场景.一个游戏可以有多个场景,每个场景负责一个地图或者一片区域.游戏界面的显示,因此场景非常重要. 1.File->New Scene(Ctrl+N)新建场景 2.F ...

  9. Mysql基础(九):MySQL 事务

    一.含义事务:一条或多条sql语句组成一个执行单位,一组sql语句要么都执行要么都不执行二.特点(ACID)A 原子性:一个事务是不可再分割的整体,要么都执行要么都不执行C 一致性:一个事务可以使数据 ...

  10. java 面向对象(二十三):关键字:abstract以及模板方法的设计模式

    abstract abstract: 抽象的1.可以用来修饰:类.方法2.具体的:abstract修饰类:抽象类 * > 此类不能实例化 * > 抽象类中一定有构造器,便于子类实例化时调用 ...