iOS奋斗iOS + RxSwift 开发专栏iOS移动开发大全

使用 RxSwift 构建不同风格的阅读模式(附 Demo)

2017-04-01  本文已影响457人  灵度Ling

捋捋思路

要实现怎么样的效果?
TogglingReadingMode

Demohttps://github.com/iJudson/RxReadingMode

应用场合

一个应用,为满足用户多变的私欲,拥有多个主题风格、不同的阅读模式(日间/夜间),这种情况喜闻乐见,像某易某音乐,就同时拥有个性换肤和夜间模式的功能


某易某音乐-夜间模式-个性换肤.JPG

但即使一个应用拥有再多的风格,本质无非是事先准备好这些不同风格的「颜色样式」、「图片样式」、「遮罩样式」而已,当你点击调整风格的按钮,所有需要替换风格的界面收到通知之后,拿到事先准备好的样式进行替换即可。

实现方式

如上所述,我们需要监听者去监听「模式切换的按钮」,当接收到用户的点击时,通知所有界面,启用另一套主题风格。因而,我们需要使用到监听模式,由于涉及到多个界面不同层次下的监听,我们自然而然会排除 「Delegate」 和 「Block」这两种监听模式,那么在非 Reactive 下,我们也只能使用通知中心这种监听模式了,但其实如果使用 Reactive ,会简单得特别多,代码质量也会高很多,而且当一个应用拥有几十个界面时,我们就不需要无止境的去移除监听者,也不需要担心因为某个界面忘了移除监听者而带来的崩溃问题,Reactive 中有一个自动移除监听者的功能,会帮我们处理好这些事情。

NotificationCenter (通知中心)

作为非 Reactive 下的唯一实现方式,虽然着实会比 Reactive 的使用繁琐很多,但是我们还是可以让它尽可能的优雅的,由于这种实现方式并不是我这篇文章的主题,所以我只特别简单的谈谈我的实现思路:

RxSwift

使用 RxSwift 构建阅读模式(日间/夜间)

我们以构建一个拥有两种风格的阅读模式为例,而多种风格的应用皮肤,与此基本是一模一样的,无非是多准备一套风格罢了。

一些再熟悉不过的基本配置

正常来说,MVVM 本就为 RxSwift 而生,项目的设计模式最好选择 MVVM 模式去设计,但是这里的主体是阅读模式的构建,为了节省大家的理解时间,我们可以稍微简单粗暴些

初始化状态

Good!没有什么问题!到这里,我们已经完成了 1/10 了。而需要添加夜间模式的控件我们已经准备好了,接下来是否为这些控件准备不同模式下的样式呢?

样式管理配置类

我们需要准备两套样式:日间模式和夜间模式。为了方便管理,我们定义样式管理类 ThemeStyleConfigs,再在其中通过结构体定义日间和夜间这两套样式:

/// 夜间模式下的样式配置
struct NightTime {
    // 主背景颜色
    static let primaryBackgroundColor = UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1.0)
    // 大标题文本颜色样式
    static let titleTextColor =  UIColor(red: 191/255.0, green: 191/255.0, blue: 191/255.0, alpha: 1.0)
    // 小标题文本颜色样式
    static let detailLabelTextColor = UIColor(red: 140/255.0, green: 140/255.0, blue: 140/255.0, alpha: 1.0)
    // 返回按钮图片样式
    static let backButtomImage = UIImage(named: "night_BackArrow@24x24")
    
}

/// 日间模式下的样式配置 (与以上对应)
struct DayTime {
    static let primaryBackgroundColor = UIColor.white
    static let titleTextColor =  UIColor(red: 63/255.0, green: 63/255.0, blue: 63/255.0, alpha: 1.0)
    static let detailLabelTextColor = UIColor(red: 101/255.0, green: 106/255.0, blue: 113/255.0, alpha: 1.0)
    static let backButtomImage = UIImage(named: "day_BackArrow@24x24")
}

而当我们想拿某个模式下的样式,即可通过以下方式:

// 如:拿「日间模式」下的主背景颜色样式
let dayTimeColor = ThemeStyleConfigs.DayTime.primaryBackgroundColor

基本的准备工作到这里就结束了,很无聊?别急,我们要开始一些不一样的工作了。

ReadingModeAdjuster - 阅读体验调节器

阅读体验调节器,即开启不同阅读模式的总开关,我们遵循封装思想,将一些复杂的逻辑封装在类内,保证外部的调用代码可读性尽可能的高,而且调用简单;

  1. 定义被外部监听的阅读模式属性,其主要用于被混和风格的样式类(下面会讲)监听,为方便使用,这里定义为类属性
// 默认值为日间模式
 static var readingMode = Variable<ReadingMode>(.dayTime)
  1. 前面说过,这里是阅读模式的总开关,即当点击控制器的模式切换开关时,我需要去修改 1 中阅读模式的值,当然我们可以直接拿到 1 中的属性去赋值,但是这样子就将没必要对控制器开放的 Observable 属性向它开放了,这并不是我们所希望的,那么我们该如何处理呢?我们通过一个对外的类方法:
// 调用该方法,即可修改 readingMode 属性并引起一连串的连锁反应
static func updateStatus(readingMode: ReadingMode = .dayTime) {
    self.readingMode.value = readingMode
}

到这里,完成了该类的配置,但还是总感觉少了点什么?这个类就只有这么点料?我们就这么冷落一个热情的 Variable 属性?

MixedStyle - 混合风格取样类

正如前面所说,我们构建该类的目的是,向该类输入一些不同的风格样式,该类就自动输出我们所需要的样式,跟自动售货机一样。听起来很复杂,但是其实很简单。

诶,但是风格类型不是有很多种?我们可能需要修改图片的背景颜色(UIColor 类型),又可能需要修改 UILabel 的字体大小(CGFloat 类型)。那....?我们这里可以使用泛型类,该类的类型会在输入一个值的时候确定。

而实际上,这个类需要做的就只有一件事情,就是输出一个具体的风格样式,但是输入这么多种风格样式,我们到底要输出哪种样式呢?这当然就取决于「上一个操作」被冷落的属性 readingMode,在该类中,我们去监听该属性的变化,怎么样的阅读模式就输出怎么样的风格样式。

  1. 定义混合风格类
struct MixedStyle<T> { 
  /// 混合风格类属性

 /// 混合风格类初始化构造器的配置

}
  1. 混合风格类属性的定义
/// 混合风格类属性
var dayTimeStyle: T // 日间模式样式输入
var nightTimeStyle: T // 夜间间模式样式输入

// outPut
var presentedStyle: Driver<T> // 外部输出属性,即呈现给外部的模式样式,因该值仅在纯 UI 下使用,故定义为 Driver
fileprivate let disposeBag = DisposeBag() // 监听者自动销毁器

  1. 混合风格类初始化构造器的配置
 /// 混合风格类初始化构造器的配置
 init(dayTime dayStyle: T, nightTime nightStyle: T) {
    
    self.dayTimeStyle = dayStyle
    self.nightTimeStyle = nightStyle
    
    // 默认日间模式 监听阅读调节器的阅读模式属性,当该属性发生变化,即更新 presentedStyle 的值
    presentedStyle = ReadingModeAdjuster.readingMode.asObservable().flatMapLatest { (readMode) -> Observable<T> in
        switch readMode {
        case .dayTime:
            return Observable.just(dayStyle)
        case .nightTime:
            return Observable.just(nightStyle)
        }
    }
    .asDriver(onErrorJustReturn: dayStyle)
 }

到这一步,我们就完全把一些配置类和管理类准备好了,那么接下来就到了使用这些类的环节。请移步到控制器类

ViewController-控制器中调用配置管理类

有了前面的配置,其实我们在控制器的使用就变得十分简单,我们需要做的只有「2件事情」

  1. 将两套样式(日间/夜间)输入到 MixedStyle 类中,并为该类的输出样式(presentedStyle)添加监听者,当该输出样式发生变化,监听者拿到这个变化值,进行 UI 属性绑定操作
// 输入两套风格样式到 MixedStyle 类中
let mixedViewColors = MixedStyle(dayTime: ThemeStyleConfigs.DayTime.primaryBackgroundColor, nightTime: ThemeStyleConfigs.NightTime.primaryBackgroundColor)
// self.view 的背景颜色去监听 MixedStyle 类的呈现风格并进行背景颜色的属性绑定
mixedViewColors.presentedStyle.drive(self.view.rx.backgroundColor).disposed(by: disposeBag)

let mixedImages = MixedStyle(dayTime: ThemeStyleConfigs.DayTime.backButtomImage, nightTime: ThemeStyleConfigs.NightTime.backButtomImage)
mixedImages.presentedStyle.drive(self.backButton.rx.normalImage).disposed(by: disposeBag)

let mixedTextColors = MixedStyle(dayTime: ThemeStyleConfigs.DayTime.titleTextColor, nightTime:  ThemeStyleConfigs.NightTime.titleTextColor)
mixedTextColors.presentedStyle.drive(self.modeTitleLabel.rx.textColor).disposed(by: disposeBag)
mixedTextColors.presentedStyle.drive(self.modeToggleLabel.rx.textColor).disposed(by: disposeBag)
mixedTextColors.presentedStyle.drive(self.themeStyleLabel.rx.textColor).disposed(by: disposeBag)

let mixedDetailTextColors = MixedStyle(dayTime: ThemeStyleConfigs.DayTime.detailLabelTextColor, nightTime:  ThemeStyleConfigs.NightTime.detailLabelTextColor)
mixedDetailTextColors.presentedStyle.drive(self.modeDetailLabel.rx.textColor).disposed(by: disposeBag)
mixedTextColors.presentedStyle.drive(self.checkButton.rx.textColor).disposed(by: disposeBag)

在这里,有个小插曲:我自定义了一些 UI 控件属性监听者,熟悉 RxSwift 的人可能可以很轻易看出,因为 RxSwift 这个第三方中并没有 UIView 背景颜色监听者,如果我们想一些 UI 属性成为监听者,我们不得不去自定义监听者,RxSwift 这个功能确定十分好用,详情可见 Reactive+Extension 类。

///自定义 UIView 背景颜色监听者
extension Reactive where Base: UIView {
    var backgroundColor: UIBindingObserver<Base, UIColor> {
        return UIBindingObserver(UIElement: base) { view, color in
            view.backgroundColor = color
        }
    }
}
  1. 监听 UISwitch 切换开关的操作,当更新 UISwitch 开关的状态时,我们相应的同步到 ReadingModeAdjuster 的 readingMode 属性,还记得之前所说的类方法?
// 当开关打开的时候 开启夜间模式 否则开启日间模式
ReadingModeAdjuster.updateStatus(readingMode: sender.isOn ? .nightTime : .dayTime)

这样子去使用该类,代码的可读性是不是高了很多,而且也很方便使用?
到这里,我们阅读模式的 Demo 也就构建完成了,效果图可见文首,而其 Demohttps://github.com/iJudson/RxReadingMode

其实,细心的朋友可能发现图片还没添加夜间模式的样式,这个后续功能大家可以试着实现下,思考如何实现才为「最优」。

希望大家在实现的过程中也有所收获...

上一篇 下一篇

猜你喜欢

热点阅读