本文首发于 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. P2136 拉近距离

    我也想有这样的爱情故事,可惜我单身 其实这道题就是一个比较裸的最短路问题.对于一个三元组 (S,W,T) ,S其实就是一个端点,而W就是到达的端点,连接两个端点的边长为-T,注意要取一个相反数,这样才 ...

  2. 关于位图数据和标记位-P3

    文章目录 1 背景 1.1 问题 2 问题1探究 2.1 没有区的情况 2.2 一个区的情况 2.3 两个区的情况 2.4 三个区的情况 2.5 四个区的情况 2.6 五个区的情况 3 问题2探究 3 ...

  3. P1004 方格取数——奇怪的dp

    P1004 方格取数 题目描述 设有 \(N\times N\) 的方格图 \((N\leq 20)\),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 \(0\) .如下图所示(见样例) ...

  4. 最全MySQL数据库表的查询操作

    序言 1.MySQL表操作(创建表,查询表结构,更改表字段等), 2.MySQL的数据类型(CHAR.VARCHAR.BLOB,等), 本节比较重要,对数据表数据进行查询操作,其中可能大家不熟悉的就对 ...

  5. 基本数据类型--------------------集合set()

    一.作用:集合.list.tuple.dict一样都可以存放多个值,但是集合主要用于:关系运算.去重 # 1.1 关系运算 friends1 = ["zero","kev ...

  6. Python OpenCV的绘图功能简介

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:大Z 在图像中我们经常需要用到将某个局部特征画出来,比如物体检测,物 ...

  7. SpringBoot系列之前后端接口安全技术JWT

    @ 目录 1. 什么是JWT? 2. JWT令牌结构怎么样? 2.1 标头(Header) 2.2 有效载荷(Playload) 2.3 签名(Signature) 3. JWT原理简单介绍 4. J ...

  8. 题解:2018级算法第三次上机 C3-Zexal的浩瀚星辰

    题目描述: 样例: 实现解释: 一道结合了火箭发射的贪心题目 知识点: 贪心,优先队列 题目分析: 根据题目描述可知,延迟后时间是正常推进的,也就是假设共有n个火箭,推迟k小时.则在到达k+1小时时, ...

  9. mongodb(二):数据库安装,部署(linux)

    1.下载安装包 wget http://fastdl.mongodb.org/linux/mongodb-linux-i686-1.8.2.tgz 下载完成后解压缩压缩包 tar zxf mongod ...

  10. df['']和df[['']]的区别