Commit be44c3c4 by Dmitriy Stepanets

Merge branch 'graph-view'

# Conflicts:
#	1Weather.xcodeproj/project.pbxproj
parents 66f0c4e5 08487ecd
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300225D69DE400A05501 /* CityConditionsCell.swift */; }; CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300225D69DE400A05501 /* CityConditionsCell.swift */; };
CD82300725D6A73F00A05501 /* CityConditionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300625D6A73E00A05501 /* CityConditionButton.swift */; }; CD82300725D6A73F00A05501 /* CityConditionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300625D6A73E00A05501 /* CityConditionButton.swift */; };
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300925D6B2AF00A05501 /* AppTabBarController.swift */; }; CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300925D6B2AF00A05501 /* AppTabBarController.swift */; };
CD9B6B1125DBC723001D9B80 /* CubicCurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9B6B1025DBC723001D9B80 /* CubicCurveAlgorithm.swift */; };
CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9B6B1325DBCDE2001D9B80 /* GraphView.swift */; };
CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CDD0F1E42572425200CF5017 /* SF-Pro.ttf */; }; CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CDD0F1E42572425200CF5017 /* SF-Pro.ttf */; };
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1E72572429E00CF5017 /* AppFont.swift */; }; CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1E72572429E00CF5017 /* AppFont.swift */; };
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */; }; CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */; };
...@@ -70,6 +72,9 @@ ...@@ -70,6 +72,9 @@
CD82300225D69DE400A05501 /* CityConditionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionsCell.swift; sourceTree = "<group>"; }; CD82300225D69DE400A05501 /* CityConditionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionsCell.swift; sourceTree = "<group>"; };
CD82300625D6A73E00A05501 /* CityConditionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionButton.swift; sourceTree = "<group>"; }; CD82300625D6A73E00A05501 /* CityConditionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionButton.swift; sourceTree = "<group>"; };
CD82300925D6B2AF00A05501 /* AppTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabBarController.swift; sourceTree = "<group>"; }; CD82300925D6B2AF00A05501 /* AppTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabBarController.swift; sourceTree = "<group>"; };
CD9B6B1025DBC723001D9B80 /* CubicCurveAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CubicCurveAlgorithm.swift; sourceTree = "<group>"; };
CD9B6B1325DBCDE2001D9B80 /* GraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = "<group>"; };
CDA69B2B2574F3C800CB6409 /* CityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CDD0F1E42572425200CF5017 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = "<group>"; }; CDD0F1E42572425200CF5017 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = "<group>"; };
CDD0F1E72572429E00CF5017 /* AppFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFont.swift; sourceTree = "<group>"; }; CDD0F1E72572429E00CF5017 /* AppFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFont.swift; sourceTree = "<group>"; };
CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; }; CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
...@@ -111,6 +116,7 @@ ...@@ -111,6 +116,7 @@
CD1237C0255D5C5900C98139 /* Products */, CD1237C0255D5C5900C98139 /* Products */,
5A59BA825D452B7FC9288086 /* Pods */, 5A59BA825D452B7FC9288086 /* Pods */,
DBFD169AA2AA6A3CB5B68BB5 /* Frameworks */, DBFD169AA2AA6A3CB5B68BB5 /* Frameworks */,
CD1AA5A625DBFD58004D521C /* Recovered References */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
...@@ -177,6 +183,14 @@ ...@@ -177,6 +183,14 @@
path = Coordinators; path = Coordinators;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CD1AA5A625DBFD58004D521C /* Recovered References */ = {
isa = PBXGroup;
children = (
CDA69B2B2574F3C800CB6409 /* CityCell.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
CD6B3038257267E2004B34B3 /* View controllers */ = { CD6B3038257267E2004B34B3 /* View controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -270,6 +284,8 @@ ...@@ -270,6 +284,8 @@
CD1237F3255D889F00C98139 /* GradientView.swift */, CD1237F3255D889F00C98139 /* GradientView.swift */,
CDD0F1E72572429E00CF5017 /* AppFont.swift */, CDD0F1E72572429E00CF5017 /* AppFont.swift */,
CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */, CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */,
CD9B6B1025DBC723001D9B80 /* CubicCurveAlgorithm.swift */,
CD9B6B1325DBCDE2001D9B80 /* GraphView.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -424,6 +440,7 @@ ...@@ -424,6 +440,7 @@
CD1237F4255D889F00C98139 /* GradientView.swift in Sources */, CD1237F4255D889F00C98139 /* GradientView.swift in Sources */,
CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */, CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */,
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */, CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */,
CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */,
CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */, CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */, CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */, CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
...@@ -434,6 +451,7 @@ ...@@ -434,6 +451,7 @@
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */, CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */,
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */, CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */,
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */, CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */,
CD9B6B1125DBC723001D9B80 /* CubicCurveAlgorithm.swift in Sources */,
CD15DB4225DA806C00024727 /* CityForecastTimePeriodCell.swift in Sources */, CD15DB4225DA806C00024727 /* CityForecastTimePeriodCell.swift in Sources */,
CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */, CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */,
CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */, CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */,
......
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "79",
"green" : "129",
"red" : "255"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "79",
"green" : "129",
"red" : "255"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "243",
"green" : "103",
"red" : "31"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "243",
"green" : "103",
"red" : "31"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -10,6 +10,7 @@ import UIKit ...@@ -10,6 +10,7 @@ import UIKit
class ForecastTimePeriodControl: UISegmentedControl { class ForecastTimePeriodControl: UISegmentedControl {
private let kTopInset: CGFloat = 2 private let kTopInset: CGFloat = 2
private let kSideInset:CGFloat = 11 private let kSideInset:CGFloat = 11
private var selectedIndexObserver:NSKeyValueObservation?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
...@@ -40,12 +41,16 @@ class ForecastTimePeriodControl: UISegmentedControl { ...@@ -40,12 +41,16 @@ class ForecastTimePeriodControl: UISegmentedControl {
layer.cornerRadius = 12 layer.cornerRadius = 12
//foreground if #available(iOS 13, *) {
let foregroundIndex = numberOfSegments let foregroundIndex = numberOfSegments
if subviews.indices.contains(foregroundIndex), if subviews.indices.contains(foregroundIndex),
let foregroundImageView = subviews[foregroundIndex] as? UIImageView let foregroundImageView = subviews[foregroundIndex] as? UIImageView
{ {
updateSegment(segmentImageView: foregroundImageView) updateSegment(segmentImageView: foregroundImageView)
}
}
else {
iOS12UpdateSegments()
} }
} }
...@@ -77,10 +82,49 @@ class ForecastTimePeriodControl: UISegmentedControl { ...@@ -77,10 +82,49 @@ class ForecastTimePeriodControl: UISegmentedControl {
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
segmentImageView.image = image segmentImageView.image = image
} }
private func iOS12UpdateSegments() {
let sortedSubviews = subviews.sorted{$0.frame.origin.x < $1.frame.origin.x}
for (index, subview) in sortedSubviews.enumerated() {
let segmentWidth = subview.bounds.width - 2
let segmentHeight = self.bounds.height - kTopInset * 2
subview.bounds = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight)
subview.layer.removeAnimation(forKey: "SelectionBounds") //this removes the weird scaling animation!
subview.layer.cornerRadius = 9
subview.clipsToBounds = false
subview.layer.shadowColor = UIColor.black.cgColor
subview.layer.shadowOpacity = 0.12
subview.layer.shadowOffset = .init(width: 0, height: 3)
subview.layer.shadowRadius = 8
//substitute with our own colored image
let imageFrame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight)
let rect = CGRect(origin: .zero, size: imageFrame.size)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = ThemeManager.currentTheme.segmentSelectedGradient.map{$0.cgColor}
gradientLayer.bounds = imageFrame
gradientLayer.cornerRadius = 9
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
gradientLayer.render(in: ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if selectedSegmentIndex == index {
subview.backgroundColor = UIColor(patternImage: image!)
}
else {
subview.backgroundColor = nil
}
}
}
} }
private extension ForecastTimePeriodControl { private extension ForecastTimePeriodControl {
func prepare() { func prepare() {
self.tintColor = .clear
self.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default) self.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
self.backgroundColor = ThemeManager.currentTheme.segmentBackgroundColor self.backgroundColor = ThemeManager.currentTheme.segmentBackgroundColor
layer.borderColor = ThemeManager.currentTheme.segmentBorderColor.cgColor layer.borderColor = ThemeManager.currentTheme.segmentBorderColor.cgColor
...@@ -92,5 +136,13 @@ private extension ForecastTimePeriodControl { ...@@ -92,5 +136,13 @@ private extension ForecastTimePeriodControl {
self.setTitleTextAttributes([.font : AppFont.SFPro.bold(size: 14), self.setTitleTextAttributes([.font : AppFont.SFPro.bold(size: 14),
.foregroundColor : ThemeManager.currentTheme.segmentSelectedTextColor], .foregroundColor : ThemeManager.currentTheme.segmentSelectedTextColor],
for: .selected) for: .selected)
self.selectedIndexObserver = self.observe(\.selectedSegmentIndex, options: .new) { (control, change) in
if #available(iOS 13, *) {
//do nothing
}
else {
self.iOS12UpdateSegments()
}
}
} }
} }
//
// CubicCurveAlgorithm.swift
// 1Weather
//
// Created by Dmitry Stepanets on 16.02.2021.
//
import UIKit
struct CubicCurveSegment
{
let controlPoint1: CGPoint
let controlPoint2: CGPoint
}
class CubicCurveAlgorithm
{
private var firstControlPoints: [CGPoint?] = []
private var secondControlPoints: [CGPoint?] = []
func controlPointsFromPoints(dataPoints: [CGPoint]) -> [CubicCurveSegment] {
//Number of Segments
let count = dataPoints.count - 1
//P0, P1, P2, P3 are the points for each segment, where P0 & P3 are the knots and P1, P2 are the control points.
if count == 1 {
let P0 = dataPoints[0]
let P3 = dataPoints[1]
//Calculate First Control Point
//3P1 = 2P0 + P3
let P1x = (2*P0.x + P3.x)/3
let P1y = (2*P0.y + P3.y)/3
firstControlPoints.append(CGPoint(x: P1x, y: P1y))
//Calculate second Control Point
//P2 = 2P1 - P0
let P2x = (2*P1x - P0.x)
let P2y = (2*P1y - P0.y)
secondControlPoints.append(CGPoint(x: P2x, y: P2y))
} else {
firstControlPoints = Array(repeating: nil, count: count)
var rhsArray = [CGPoint]()
//Array of Coefficients
var a = [CGFloat]()
var b = [CGFloat]()
var c = [CGFloat]()
for i in 0..<count {
var rhsValueX: CGFloat = 0
var rhsValueY: CGFloat = 0
let P0 = dataPoints[i];
let P3 = dataPoints[i+1];
if i==0 {
a.append(0)
b.append(2)
c.append(1)
//rhs for first segment
rhsValueX = P0.x + 2*P3.x;
rhsValueY = P0.y + 2*P3.y;
} else if i == count-1 {
a.append(2)
b.append(7)
c.append(0)
//rhs for last segment
rhsValueX = 8*P0.x + P3.x;
rhsValueY = 8*P0.y + P3.y;
} else {
a.append(1)
b.append(4)
c.append(1)
rhsValueX = 4*P0.x + 2*P3.x;
rhsValueY = 4*P0.y + 2*P3.y;
}
rhsArray.append(CGPoint(x: rhsValueX, y: rhsValueY))
}
//Solve Ax=B. Use Tridiagonal matrix algorithm a.k.a Thomas Algorithm
for i in 1..<count {
let rhsValueX = rhsArray[i].x
let rhsValueY = rhsArray[i].y
let prevRhsValueX = rhsArray[i-1].x
let prevRhsValueY = rhsArray[i-1].y
let m = CGFloat(a[i]/b[i-1])
let b1 = b[i] - m * c[i-1];
b[i] = b1
let r2x = rhsValueX - m * prevRhsValueX
let r2y = rhsValueY - m * prevRhsValueY
rhsArray[i] = CGPoint(x: r2x, y: r2y)
}
//Get First Control Points
//Last control Point
let lastControlPointX = rhsArray[count-1].x/b[count-1]
let lastControlPointY = rhsArray[count-1].y/b[count-1]
firstControlPoints[count-1] = CGPoint(x: lastControlPointX, y: lastControlPointY)
for i in (0 ..< count - 1).reversed() {
if let nextControlPoint = firstControlPoints[i+1] {
let controlPointX = (rhsArray[i].x - c[i] * nextControlPoint.x)/b[i]
let controlPointY = (rhsArray[i].y - c[i] * nextControlPoint.y)/b[i]
firstControlPoints[i] = CGPoint(x: controlPointX, y: controlPointY)
}
}
//Compute second Control Points from first
for i in 0..<count {
if i == count-1 {
let P3 = dataPoints[i+1]
guard let P1 = firstControlPoints[i] else{
continue
}
let controlPointX = (P3.x + P1.x)/2
let controlPointY = (P3.y + P1.y)/2
secondControlPoints.append(CGPoint(x: controlPointX, y: controlPointY))
} else {
let P3 = dataPoints[i+1]
guard let nextP1 = firstControlPoints[i+1] else {
continue
}
let controlPointX = 2*P3.x - nextP1.x
let controlPointY = 2*P3.y - nextP1.y
secondControlPoints.append(CGPoint(x: controlPointX, y: controlPointY))
}
}
}
var controlPoints = [CubicCurveSegment]()
for i in 0..<count {
if let firstControlPoint = firstControlPoints[i],
let secondControlPoint = secondControlPoints[i] {
let segment = CubicCurveSegment(controlPoint1: firstControlPoint, controlPoint2: secondControlPoint)
controlPoints.append(segment)
}
}
return controlPoints
}
}
//
// GraphView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 16.02.2021.
//
import UIKit
class GraphView: UIView {
//Private
private let kDotRadius:CGFloat = 3
private let graphColor:UIColor
private let graphTintColor:UIColor
private let lineShape = CAShapeLayer()
private let cubicCurveAlgorithm = CubicCurveAlgorithm()
init(graphColor:UIColor, tintColor:UIColor) {
self.graphColor = graphColor
self.graphTintColor = tintColor
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//Public
public func drawGraph(with points:[CGPoint]) {
//Clean up
self.layer.sublayers?.forEach {
$0.removeFromSuperlayer()
}
guard !points.isEmpty else { return }
//Add points
points.forEach {
self.addDot(point: $0)
}
//Add line
lineShape.frame = self.bounds
lineShape.path = self.linePath(from: points).cgPath
lineShape.fillColor = UIColor.clear.cgColor
lineShape.strokeColor = graphColor.cgColor
lineShape.lineWidth = 3
lineShape.lineCap = .round
lineShape.lineJoin = .round
//Shadow
lineShape.shadowColor = UIColor.black.cgColor
lineShape.shadowOffset = .init(width: 0, height: 6)
lineShape.shadowRadius = 3
lineShape.shadowOpacity = 0.1
layer.insertSublayer(lineShape, at: 0)
}
public func tintGraph(at startPoint:CGPoint, width:CGFloat) {
let tintShape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: startPoint)
}
//Private
private func linePath(from points:[CGPoint]) -> UIBezierPath {
let path = UIBezierPath()
let startPoint = CGPoint(x: 0, y: self.frame.height)
let endPoint = CGPoint(x: self.frame.width, y: self.frame.height)
var pointsToAdd = [CGPoint]()
pointsToAdd.append(startPoint)
pointsToAdd.append(contentsOf: points)
pointsToAdd.append(endPoint)
path.move(to: pointsToAdd.first!)
if pointsToAdd.count == 2 {
path.addLine(to: points[1])
return path
}
let controlPoints = cubicCurveAlgorithm.controlPointsFromPoints(dataPoints: pointsToAdd)
for index in 1..<pointsToAdd.count {
path.addCurve(to: pointsToAdd[index],
controlPoint1: controlPoints[index - 1].controlPoint1,
controlPoint2: controlPoints[index - 1].controlPoint2)
}
return path
}
private func addDot(point:CGPoint) {
let dotShape = CAShapeLayer()
dotShape.path = UIBezierPath(arcCenter: point,
radius: kDotRadius,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true).cgPath
dotShape.fillColor = UIColor.white.cgColor
dotShape.strokeColor = self.graphColor.cgColor
dotShape.lineWidth = 2
layer.addSublayer(dotShape)
}
}
...@@ -79,4 +79,13 @@ struct DefaultTheme: ThemeProtocol { ...@@ -79,4 +79,13 @@ struct DefaultTheme: ThemeProtocol {
var segmentBackgroundColor: UIColor { var segmentBackgroundColor: UIColor {
return UIColor(named: "segment_bg_color") ?? .red return UIColor(named: "segment_bg_color") ?? .red
} }
//Graph
var graphColor: UIColor {
return UIColor(named: "graph_color") ?? .red
}
var graphTintColor: UIColor {
return UIColor(named: "graph_tint_color") ?? .red
}
} }
...@@ -35,4 +35,8 @@ protocol ThemeProtocol { ...@@ -35,4 +35,8 @@ protocol ThemeProtocol {
var segmentBackgroundColor:UIColor { get } var segmentBackgroundColor:UIColor { get }
var segmentBorderColor:UIColor { get } var segmentBorderColor:UIColor { get }
var segmentSelectedGradient:[UIColor] { get } var segmentSelectedGradient:[UIColor] { get }
//Graph
var graphColor:UIColor { get }
var graphTintColor:UIColor { get }
} }
...@@ -17,9 +17,9 @@ class CityForecastTimePeriodCell: UITableViewCell { ...@@ -17,9 +17,9 @@ class CityForecastTimePeriodCell: UITableViewCell {
private let summaryView = UIView() private let summaryView = UIView()
private let summaryImageView = UIImageView() private let summaryImageView = UIImageView()
private let summaryLabel = UILabel() private let summaryLabel = UILabel()
private let graphLayer = CALayer() private let graphView = GraphView(graphColor: ThemeManager.currentTheme.graphColor,
private let lineLayer = CAShapeLayer() tintColor: ThemeManager.currentTheme.graphTintColor)
private let tempsArray = [24,25,26,25,20,21,27,24,24,20,21,25] private let tempsArray = [20,18,25,24,22,20,23,25,27,26,23,20]
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) super.init(style: style, reuseIdentifier: reuseIdentifier)
...@@ -28,6 +28,7 @@ class CityForecastTimePeriodCell: UITableViewCell { ...@@ -28,6 +28,7 @@ class CityForecastTimePeriodCell: UITableViewCell {
prepareSegmentedControl() prepareSegmentedControl()
prepareScrollView() prepareScrollView()
prepareStackView() prepareStackView()
prepareGraphView()
prepareSummaryView() prepareSummaryView()
} }
...@@ -44,41 +45,34 @@ class CityForecastTimePeriodCell: UITableViewCell { ...@@ -44,41 +45,34 @@ class CityForecastTimePeriodCell: UITableViewCell {
guard let periodButton = stackView.arrangedSubviews.first as? PeriodForecastButton else { return } guard let periodButton = stackView.arrangedSubviews.first as? PeriodForecastButton else { return }
let graphRect = periodButton.getGraphRect() let graphRect = periodButton.getGraphRect()
guard graphRect.width > 0 else { return } guard graphRect.width > 0 else { return }
graphView.frame = .init(x: 0, y: graphRect.origin.y, width: scrollView.contentSize.width, height: graphRect.height)
//Add graph layer if needed //Draw points and lines
if scrollView.layer.sublayers?.contains(graphLayer) == false { self.graphView.drawGraph(with: self.getGraphPoints())
graphLayer.backgroundColor = UIColor.red.cgColor }
scrollView.layer.addSublayer(graphLayer)
} private func getGraphPoints() -> [CGPoint] {
var points = [CGPoint]()
graphLayer.frame = .init(x: 0, y: graphRect.origin.y, width: scrollView.contentSize.width, height: graphRect.height)
//Draw points
let maxTemp = CGFloat(tempsArray.max() ?? 0) let maxTemp = CGFloat(tempsArray.max() ?? 0)
let minTemp = CGFloat(tempsArray.min() ?? 0) let minTemp = CGFloat(tempsArray.min() ?? 0)
for index in 0..<stackView.arrangedSubviews.count { for index in 0..<stackView.arrangedSubviews.count {
guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue } guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue }
let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width
let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2 let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2
let numberOfLevels = maxTemp - minTemp let numberOfLevels = maxTemp - minTemp
let levelHeight = graphRect.height / numberOfLevels let levelHeight = (graphView.frame.height / numberOfLevels).rounded(.down)
let pointLevel = ((maxTemp - CGFloat(tempsArray[index])) * levelHeight) + 5 var pointLevel = (maxTemp - CGFloat(tempsArray[index])) * levelHeight
let center = CGPoint(x: buttonCenterX,
y: pointLevel - 10) //Add points offset at cirle radius if needed
print("Button center: \(center)") pointLevel = max(pointLevel, 5)
let circleShape = CAShapeLayer() pointLevel = min(pointLevel, graphView.frame.height - 5)
circleShape.path = UIBezierPath(arcCenter: center,
radius: 5, points.append(.init(x: buttonCenterX, y: pointLevel))
startAngle: 0,
endAngle: .pi * 2,
clockwise: true).cgPath
circleShape.fillColor = UIColor.white.cgColor
circleShape.strokeColor = UIColor.blue.cgColor
circleShape.lineWidth = 2
graphLayer.addSublayer(circleShape)
} }
return points
} }
} }
...@@ -118,6 +112,8 @@ private extension CityForecastTimePeriodCell { ...@@ -118,6 +112,8 @@ private extension CityForecastTimePeriodCell {
stackView.alignment = .center stackView.alignment = .center
stackView.spacing = 10 stackView.spacing = 10
stackView.clipsToBounds = false stackView.clipsToBounds = false
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 6, bottom: 0, right: 6)
scrollView.addSubview(stackView) scrollView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in stackView.snp.makeConstraints { (make) in
...@@ -130,6 +126,13 @@ private extension CityForecastTimePeriodCell { ...@@ -130,6 +126,13 @@ private extension CityForecastTimePeriodCell {
} }
} }
func prepareGraphView() {
//Graph view
graphView.frame = .zero
graphView.backgroundColor = .clear
scrollView.addSubview(graphView)
}
func prepareSummaryView() { func prepareSummaryView() {
summaryImageView.contentMode = .scaleAspectFit summaryImageView.contentMode = .scaleAspectFit
summaryImageView.image = UIImage(named: "hot_indicator") summaryImageView.image = UIImage(named: "hot_indicator")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment