♻️ :: Moved logic from view to view-model

This commit is contained in:
Felix Mau
2019-08-26 17:12:26 +02:00
parent 15b5828aa5
commit dcbb4df26e
3 changed files with 117 additions and 24 deletions
+4
View File
@@ -38,6 +38,7 @@
4B5A5EBA97A912894C10D040B5A1225D /* Pods-GradientLoadingBar_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = B8B5E60915249521CF9DC7208F2D5774 /* Pods-GradientLoadingBar_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
604C51928D4CFDAE15A6CAB6327B68D6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B802EDB707F8CE8EBD66EB815051159F /* Foundation.framework */; };
640E8ABC2313DD280008C5A6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640E8ABB2313DD280008C5A6 /* Constants.swift */; };
640E8ABE2313E4540008C5A6 /* GradientActivityIndicatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640E8ABD2313E4540008C5A6 /* GradientActivityIndicatorViewModel.swift */; };
681130A53798D8143A2C972C3351D165 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B802EDB707F8CE8EBD66EB815051159F /* Foundation.framework */; };
757352462D522BDB0B228DE9880106ED /* Pods-GradientLoadingBar_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E014E4CD6DE59FD4C7C3D12C2BC9474 /* Pods-GradientLoadingBar_Tests-dummy.m */; };
7D00DA81B76090CC52ADBEC7E56F8A4B /* GradientLoadingBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D0266A0CC59CB31FD536223C0F4096 /* GradientLoadingBarController.swift */; };
@@ -125,6 +126,7 @@
5459A35FCB502D3A65BE88C4A1A7C819 /* GradientActivityIndicatorView+AnimateIsHidden.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientActivityIndicatorView+AnimateIsHidden.swift"; sourceTree = "<group>"; };
59286ACC8816C332C7919AEBF42BC6EB /* GradientLoadingBar.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = GradientLoadingBar.podspec; sourceTree = "<group>"; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
640E8ABB2313DD280008C5A6 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
640E8ABD2313E4540008C5A6 /* GradientActivityIndicatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorViewModel.swift; sourceTree = "<group>"; };
670D1598351BCDDA2ACA6F3ECB0434BB /* SwiftFormat.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftFormat.xcconfig; sourceTree = "<group>"; };
67873E92210DD4E777C49C6E8AEDC108 /* GradientLoadingBar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GradientLoadingBar.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7471906D53E2B2BEDC58FEDF3DC82244 /* Pods-GradientLoadingBar_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-GradientLoadingBar_Example-dummy.m"; sourceTree = "<group>"; };
@@ -302,6 +304,7 @@
isa = PBXGroup;
children = (
8AB9BDBFBE2396FB45EEB12E7B9C6067 /* GradientLoadingBarViewModel.swift */,
640E8ABD2313E4540008C5A6 /* GradientActivityIndicatorViewModel.swift */,
);
name = ViewModel;
path = GradientLoadingBar/Classes/ViewModel;
@@ -625,6 +628,7 @@
8BD7971BD44E1BB5E96B9BD7F1047F0F /* GradientActivityIndicatorView+AnimateIsHidden.swift in Sources */,
B4876567964EBC1B95C76559240D1AAB /* GradientActivityIndicatorView.swift in Sources */,
7F0493B920FABFACCE19457C63AD9719 /* GradientLoadingBar-dummy.m in Sources */,
640E8ABE2313E4540008C5A6 /* GradientActivityIndicatorViewModel.swift in Sources */,
7D00DA81B76090CC52ADBEC7E56F8A4B /* GradientLoadingBarController.swift in Sources */,
3F7EF08A0DB32C7543CFDDB67068D548 /* GradientLoadingBarViewModel.swift in Sources */,
640E8ABC2313DD280008C5A6 /* Constants.swift in Sources */,
@@ -0,0 +1,73 @@
//
// GradientActivityIndicatorViewModel.swift
// GradientLoadingBar
//
// Created by Felix Mau on 08/26/19.
// Copyright © 2019 Felix Mau. All rights reserved.
//
import Foundation
import LightweightObservable
class GradientActivityIndicatorViewModel {
// MARK: - Types
///
enum AnimationState: Equatable {
case none
case animating(duration: TimeInterval)
}
// MARK: - Public properties
///
var animationState: Observable<AnimationState> {
return animationStateSubject.asObservable
}
///
var infinteGradientColors: Observable<[CGColor]> {
return infinteGradientColorsSubject.asObservable
}
///
var isHidden = false {
didSet {
if isHidden {
animationStateSubject.value = .none
} else {
animationStateSubject.value = .animating(duration: progressAnimationDuration)
}
}
}
/// Colors used for the gradient.
var gradientColors = UIColor.GradientLoadingBar.gradientColors {
didSet {
infinteGradientColorsSubject.value = makeInfiniteGradientColors()
}
}
/// Duration for the progress animation.
var progressAnimationDuration = TimeInterval.GradientLoadingBar.progressDuration
// MARK: - Private properties
private let infinteGradientColorsSubject: Variable<[CGColor]> = Variable([])
private let animationStateSubject: Variable<AnimationState> = Variable(.none)
// MARK: - Private methods
/// Simulate infinte animation - Therefore we'll reverse the colors and remove the first and last item
/// to prevent duplicate values at the "inner edges" destroying the infinite look.
private func makeInfiniteGradientColors() -> [CGColor] {
let reversedColors = gradientColors
.reversed()
.dropFirst()
.dropLast()
let infiniteGradientColors = gradientColors + reversedColors + gradientColors
return infiniteGradientColors.map { $0.cgColor }
}
}
@@ -7,6 +7,7 @@
//
import UIKit
import LightweightObservable
@IBDesignable
open class GradientActivityIndicatorView: UIView {
@@ -19,37 +20,32 @@ open class GradientActivityIndicatorView: UIView {
open override var isHidden: Bool {
didSet {
if isHidden {
stopProgressAnimation()
} else {
startProgressAnimation()
}
viewModel.isHidden = isHidden
}
}
/// Duration for the progress animation.
public var progressAnimationDuration = TimeInterval.GradientLoadingBar.progressDuration
public var progressAnimationDuration: TimeInterval {
get {
return viewModel.progressAnimationDuration
}
set {
viewModel.progressAnimationDuration = newValue
}
}
/// Colors used for the gradient.
public var gradientColorList = UIColor.GradientLoadingBar.gradientColorList {
didSet {
gradientLayer.colors = infinteColorList.map { $0.cgColor }
public var gradientColors: [UIColor] {
get {
return viewModel.gradientColors
}
set {
viewModel.gradientColors = newValue
}
}
// MARK: - Private properties
/// Simulate infinte animation - Therefore we'll reverse the colors and remove the first and last item
/// to prevent duplicate values at the "inner edges" destroying the infinite look.
private var infinteColorList: [UIColor] {
let reversedColorList = gradientColorList
.reversed()
.dropFirst()
.dropLast()
return gradientColorList + reversedColorList + gradientColorList
}
/// Layer holding the gradient.
private var gradientLayer: CAGradientLayer = {
let layer = CAGradientLayer()
@@ -61,6 +57,12 @@ open class GradientActivityIndicatorView: UIView {
return layer
}()
///
private let viewModel = GradientActivityIndicatorViewModel()
/// The dispose bag for the observables.
private var disposeBag = DisposeBag()
// MARK: - Initializers
public override init(frame: CGRect) {
@@ -102,21 +104,35 @@ open class GradientActivityIndicatorView: UIView {
// MARK: - Private methods
private func commonInit() {
gradientLayer.colors = infinteColorList.map { $0.cgColor }
layer.insertSublayer(gradientLayer, at: 0)
layer.masksToBounds = true
startProgressAnimation()
bindViewModelToView()
}
private func startProgressAnimation() {
private func bindViewModelToView() {
viewModel.infinteGradientColors.subscribeDistinct { [weak self] newInfinteGradientColors, _ in
self?.gradientLayer.colors = newInfinteGradientColors
}.disposed(by: &disposeBag)
viewModel.animationState.subscribeDistinct { [weak self] newAnimationState, _ in
switch newAnimationState {
case .none:
self?.stopProgressAnimation()
case let .animating(duration):
self?.startProgressAnimation(duration: duration)
}
}.disposed(by: &disposeBag)
}
private func startProgressAnimation(duration: TimeInterval) {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(x: -2 * bounds.size.width, y: 0)
animation.toValue = CGPoint.zero
animation.isRemovedOnCompletion = false
animation.repeatCount = Float.infinity
animation.duration = progressAnimationDuration
animation.duration = duration
gradientLayer.add(animation, forKey: GradientActivityIndicatorView.progressAnimationKey)
}