Commit e8b2c07d by Dmitriy Stepanets

Merge branch 'master' into forecast-controller

# Conflicts:
#	1Weather.xcworkspace/xcuserdata/dstepanets.xcuserdatad/UserInterfaceState.xcuserstate
#	1Weather/UI/View controllers/Today/Cells/CityForecastTimePeriod/CityForecastTimePeriodCell.swift
parents 5faf43ad 81e13891
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; }; CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; };
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; }; CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; };
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B304225726AD1004B34B3 /* DefaultTheme.swift */; }; CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B304225726AD1004B34B3 /* DefaultTheme.swift */; };
CD71709025FA317700A63C27 /* ForecastTimePeriodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71708F25FA317700A63C27 /* ForecastTimePeriodView.swift */; };
CD71709325FA31C200A63C27 /* ForecastTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71709225FA31C200A63C27 /* ForecastTimePeriod.swift */; };
CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */; }; CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */; };
CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD822FF425D6817000A05501 /* CityForecastCell.swift */; }; CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD822FF425D6817000A05501 /* CityForecastCell.swift */; };
CD822FFA25D6890900A05501 /* OneWeatherColorsAsset.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */; }; CD822FFA25D6890900A05501 /* OneWeatherColorsAsset.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */; };
...@@ -113,6 +115,8 @@ ...@@ -113,6 +115,8 @@
CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingButton.swift; sourceTree = "<group>"; }; CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingButton.swift; sourceTree = "<group>"; };
CD6B303D25726960004B34B3 /* ThemeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeProtocol.swift; sourceTree = "<group>"; }; CD6B303D25726960004B34B3 /* ThemeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeProtocol.swift; sourceTree = "<group>"; };
CD6B304225726AD1004B34B3 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; }; CD6B304225726AD1004B34B3 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; };
CD71708F25FA317700A63C27 /* ForecastTimePeriodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastTimePeriodView.swift; sourceTree = "<group>"; };
CD71709225FA31C200A63C27 /* ForecastTimePeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastTimePeriod.swift; sourceTree = "<group>"; };
CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = "<group>"; }; CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = "<group>"; };
CD822FF425D6817000A05501 /* CityForecastCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityForecastCell.swift; sourceTree = "<group>"; }; CD822FF425D6817000A05501 /* CityForecastCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityForecastCell.swift; sourceTree = "<group>"; };
CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OneWeatherColorsAsset.xcassets; sourceTree = "<group>"; }; CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OneWeatherColorsAsset.xcassets; sourceTree = "<group>"; };
...@@ -435,6 +439,7 @@ ...@@ -435,6 +439,7 @@
CDC6126525E9085600188DA7 /* GraphLine.swift */, CDC6126525E9085600188DA7 /* GraphLine.swift */,
CD86246B25E6826A0097F3FB /* InnerShadowLayer.swift */, CD86246B25E6826A0097F3FB /* InnerShadowLayer.swift */,
CDC6126925E90C8800188DA7 /* GraphLineSettings.swift */, CDC6126925E90C8800188DA7 /* GraphLineSettings.swift */,
CD71708F25FA317700A63C27 /* ForecastTimePeriodView.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -508,6 +513,7 @@ ...@@ -508,6 +513,7 @@
CEAFF08B25DFC6BC00DF4EBF /* DailyWeather.swift */, CEAFF08B25DFC6BC00DF4EBF /* DailyWeather.swift */,
CEAFF08E25DFC6ED00DF4EBF /* HourlyWeather.swift */, CEAFF08E25DFC6ED00DF4EBF /* HourlyWeather.swift */,
CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */, CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */,
CD71709225FA31C200A63C27 /* ForecastTimePeriod.swift */,
); );
path = ModelObjects; path = ModelObjects;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -679,6 +685,7 @@ ...@@ -679,6 +685,7 @@
CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */, CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */,
CD39F2EE25DE858D009FE398 /* NotificationName+Localization.swift in Sources */, CD39F2EE25DE858D009FE398 /* NotificationName+Localization.swift in Sources */,
CEAFF08F25DFC6ED00DF4EBF /* HourlyWeather.swift in Sources */, CEAFF08F25DFC6ED00DF4EBF /* HourlyWeather.swift in Sources */,
CD71709025FA317700A63C27 /* ForecastTimePeriodView.swift in Sources */,
CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */, CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */, CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */, CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */,
...@@ -692,6 +699,7 @@ ...@@ -692,6 +699,7 @@
CDEE8AD725DA882200C289DE /* PeriodForecastButton.swift in Sources */, CDEE8AD725DA882200C289DE /* PeriodForecastButton.swift in Sources */,
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */, CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */, CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CD71709325FA31C200A63C27 /* ForecastTimePeriod.swift in Sources */,
CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */, CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */,
CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */, CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */,
CDC6125325E79C8F00188DA7 /* DayTimeView.swift in Sources */, CDC6125325E79C8F00188DA7 /* DayTimeView.swift in Sources */,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<key>1Weather.xcscheme_^#shared#^_</key> <key>1Weather.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>5</integer> <integer>6</integer>
</dict> </dict>
<key>PG (Playground) 1.xcscheme</key> <key>PG (Playground) 1.xcscheme</key>
<dict> <dict>
......
...@@ -33,6 +33,8 @@ extension Measurement { ...@@ -33,6 +33,8 @@ extension Measurement {
extension Temperature { extension Temperature {
var localeValue:Double { var localeValue:Double {
#warning("Hardcoded value!")
// TODO: replace .fahrenheit with a value from Settings.
return self.converted(to: .fahrenheit).value.rounded(.down) return self.converted(to: .fahrenheit).value.rounded(.down)
} }
} }
//
// ForecastTimePeriod.swift
// 1Weather
//
// Created by Dmitry Stepanets on 11.03.2021.
//
import UIKit
struct ForecastTimePeriod {
let daily:[DailyWeather]
let hourly:[HourlyWeather]
}
//
// ForecastView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 11.03.2021.
//
import UIKit
private struct DailyGraphPoints {
let maxTempPoints: [CGPoint]
let minTempPoints: [CGPoint]
}
private struct HourlyGraphPoints {
let points: [CGPoint]
}
class ForecastTimePeriodView: UIView {
//Private
private let scrollView = UIScrollView()
private let stackView = UIStackView()
private let graphView = GraphView()
private var graphRect:CGRect = .zero
private var currentTimePeriod = TimePeriod.daily
private var dailyGraphPoints = DailyGraphPoints(maxTempPoints: [CGPoint](), minTempPoints: [CGPoint]())
private var hourlyGraphPoints = HourlyGraphPoints(points: [CGPoint]())
private var forecastTimePeriod:ForecastTimePeriod = ForecastTimePeriod(daily: [DailyWeather](), hourly: [HourlyWeather]()) {
didSet {
rebuildButtons()
}
}
init() {
super.init(frame: .zero)
preapreView()
prepareScrollView()
prepareStackView()
prepareGraphView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//Public
public func set(forecastTimePeriod: ForecastTimePeriod) {
self.forecastTimePeriod = forecastTimePeriod
}
public func set(timePeriod:TimePeriod) {
self.currentTimePeriod = timePeriod
rebuildButtons()
}
//Private
private func rebuildButtons() {
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
switch currentTimePeriod {
case .daily:
for index in 0..<forecastTimePeriod.daily.count {
let forecastButton = PeriodForecastButton()
forecastButton.configure(dailyWeather: forecastTimePeriod.daily[index])
forecastButton.index = index
forecastButton.addTarget(self, action: #selector(handleForecastButton(button:)), for: .touchUpInside)
forecastButton.isSelected = index == 1
stackView.addArrangedSubview(forecastButton)
}
case .hourly:
for index in 0..<forecastTimePeriod.hourly.count {
let forecastButton = PeriodForecastButton()
forecastButton.configure(hourlyWeather: forecastTimePeriod.hourly[index])
forecastButton.index = index
forecastButton.addTarget(self, action: #selector(handleForecastButton(button:)), for: .touchUpInside)
forecastButton.isSelected = index == 1
stackView.addArrangedSubview(forecastButton)
}
}
stackView.layoutIfNeeded()
updateGraphLayout()
}
private func updateGraphLayout() {
print("[ForecastTimePeriod] Update graph layout")
graphRect = (stackView.arrangedSubviews.first as? PeriodForecastButton)?.graphRect ?? .zero
graphView.frame = .init(x: 0,
y: graphRect.origin.y,
width: stackView.frame.width,
height: graphRect.height)
updateGraphPoints()
drawGraph()
}
private func updateGraphPoints() {
switch currentTimePeriod {
case .daily:
updateDailyGraphPoints()
case .hourly:
updateHourlyGraphPoints()
}
}
private func updateDailyGraphPoints() {
let daysCount = forecastTimePeriod.daily.count
let maxTemps = (forecastTimePeriod.daily.map{ CGFloat($0.maxTemp?.localeValue ?? 0) })
let topMaxTemp = maxTemps.max() ?? 0
let minTemps = (forecastTimePeriod.daily.map{ CGFloat($0.minTemp?.localeValue ?? 0) })
let topMinTemp = minTemps.max() ?? 0
var maxPoints = [CGPoint]()
var minPoints = [CGPoint]()
for index in 0..<daysCount {
guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue }
let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width
let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2
let maxTempLevelsCount = CGFloat(Set(maxTemps).count)
let minTempLevelsCount = CGFloat(Set(minTemps).count)
let totalLevels = maxTempLevelsCount + minTempLevelsCount
let levelHeight = (graphView.frame.height / CGFloat(totalLevels)).rounded(.down)
let maxTempFrame = CGRect(x: 0, y: 0, width: graphView.frame.width, height: maxTempLevelsCount * levelHeight)
let minTempFrame = CGRect(x: 0, y: maxTempFrame.height, width: graphView.frame.width, height: minTempLevelsCount * levelHeight)
//Max
var maxPointLevel = maxTempFrame.origin.y + ((topMaxTemp - maxTemps[index]) * levelHeight) + levelHeight
maxPointLevel = min(maxPointLevel, maxTempFrame.height + 5)
//Min
var minPointLevel = minTempFrame.origin.y + ((topMinTemp - minTemps[index]) * levelHeight) + levelHeight
minPointLevel = min(minPointLevel, minTempFrame.height + minTempFrame.origin.y - 5)
maxPoints.append(.init(x: buttonCenterX, y: maxPointLevel))
minPoints.append(.init(x: buttonCenterX, y: minPointLevel))
}
dailyGraphPoints = DailyGraphPoints(maxTempPoints: maxPoints, minTempPoints: minPoints)
}
private func updateHourlyGraphPoints() {
let hoursCount = forecastTimePeriod.hourly.count
let temps = (forecastTimePeriod.hourly.map{ CGFloat($0.temp?.localeValue ?? 0) })
let maxTemp = temps.max() ?? 0
var points = [CGPoint]()
for index in 0..<hoursCount {
guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue }
let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width
let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2
let levelsCount = CGFloat(Set(temps).count)
let levelHeight = (graphView.frame.height / CGFloat(levelsCount)).rounded(.down)
let tempFrame = CGRect(x: 0, y: 0, width: graphView.frame.width, height: graphView.frame.height)
var pointLevel = tempFrame.origin.y + ((maxTemp - temps[index]) * levelHeight)
pointLevel = max(pointLevel, tempFrame.origin.y + 10)
pointLevel = min(tempFrame.height - 10, pointLevel)
points.append(.init(x: buttonCenterX, y: pointLevel))
}
hourlyGraphPoints = HourlyGraphPoints(points: points)
}
private func drawGraph() {
guard
let periodButtons = stackView.arrangedSubviews as? [PeriodForecastButton],
let selectedButton = (periodButtons.first{ $0.isSelected })
else {
return
}
switch currentTimePeriod {
case .daily:
self.graphView.drawMainGraph(with: dailyGraphPoints.maxTempPoints)
self.graphView.drawAdditionalGraph(with: dailyGraphPoints.minTempPoints)
self.tintGraphAt(button: selectedButton)
case .hourly:
self.graphView.drawMainGraph(with: hourlyGraphPoints.points)
self.graphView.drawAdditionalGraph(with: [CGPoint]())
self.tintGraphAt(button: selectedButton)
}
print("[ForecastTimePeriod] Draw graph")
}
private func tintGraphAt(button:PeriodForecastButton) {
switch currentTimePeriod {
case .daily:
self.graphView.tintGraphFrom(startPointX: button.frame.origin.x,
endPointX: button.frame.origin.x + button.bounds.width)
self.graphView.tintMainDotAt(point: dailyGraphPoints.maxTempPoints[button.index])
self.graphView.tintAdditionalDotAt(point: dailyGraphPoints.minTempPoints[button.index])
case .hourly:
self.graphView.tintGraphFrom(startPointX: button.frame.origin.x,
endPointX: button.frame.origin.x + button.bounds.width)
self.graphView.tintMainDotAt(point: hourlyGraphPoints.points[button.index])
}
}
@objc private func handleForecastButton(button:PeriodForecastButton) {
stackView.arrangedSubviews.forEach {
if let periodButton = $0 as? PeriodForecastButton {
periodButton.isSelected = periodButton === button
if periodButton.isSelected {
tintGraphAt(button: periodButton)
}
}
}
}
}
private extension ForecastTimePeriodView {
func preapreView() {
backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.clipsToBounds = false
addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 10
stackView.clipsToBounds = false
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 6, bottom: 0, right: 6)
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.height.equalToSuperview()
}
}
func prepareGraphView() {
//Graph view
graphView.frame = .zero
graphView.backgroundColor = .clear
scrollView.addSubview(graphView)
}
}
...@@ -7,43 +7,21 @@ ...@@ -7,43 +7,21 @@
import UIKit import UIKit
private struct DailyGraphPoints {
let maxTempPoints: [CGPoint]
let minTempPoints: [CGPoint]
}
private struct HourlyGraphPoints {
let points: [CGPoint]
}
class ForecastTimePeriodCell: UITableViewCell { class ForecastTimePeriodCell: UITableViewCell {
//Private //Private
private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(), private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(),
"forecast.timePeriod.hourly".localized()]) "forecast.timePeriod.hourly".localized()])
private let kMinGraphHeight:CGFloat = 20
private let gradientView = GradientView(startColor: UIColor(hex: 0xffffff).withAlphaComponent(0), private let gradientView = GradientView(startColor: UIColor(hex: 0xffffff).withAlphaComponent(0),
endColor: UIColor(hex: 0xdaddec), endColor: UIColor(hex: 0xdaddec),
opacity: 0.5) opacity: 0.5)
private let scrollView = UIScrollView()
private let stackView = UIStackView()
private var location:Location?
private var currentTimePeriod = TimePeriod.daily
private var dailyGraphPoints: DailyGraphPoints?
private var hourlyGraphPoints: HourlyGraphPoints?
private let graphView = GraphView()
private var graphIsDrawn = false
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)
prepareCell() prepareCell()
prepareSegmentedControl() prepareSegmentedControl()
prepareGradient() prepareGradient()
prepareScrollView()
prepareStackView()
prepareGraphView()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
...@@ -52,64 +30,6 @@ class ForecastTimePeriodCell: UITableViewCell { ...@@ -52,64 +30,6 @@ class ForecastTimePeriodCell: UITableViewCell {
//Public //Public
public func configure(with location:Location) { public func configure(with location:Location) {
self.location = location
rebuildStackButtons()
}
public func drawGraphIfNeeded() {
guard graphIsDrawn == false else { return }
//Checking for correct height
guard
let periodButton = stackView.arrangedSubviews.first as? PeriodForecastButton,
periodButton.graphRect.height >= kMinGraphHeight
else {
return
}
graphView.frame = .init(x: 0,
y: periodButton.graphRect.origin.y,
width: stackView.frame.width,
height: periodButton.graphRect.height)
updateGraphPoints()
drawGraph()
graphIsDrawn = true
}
//Private
private func rebuildStackButtons() {
//Update graph buttons
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
guard let location = self.location else { return }
switch currentTimePeriod {
case .daily:
for index in 0..<location.daily.count {
let conditionButton = PeriodForecastButton()
conditionButton.configure(dailyWeather: location.daily[index])
conditionButton.index = index
conditionButton.addTarget(self, action: #selector(handleConditionButton(button:)), for: .touchUpInside)
conditionButton.isSelected = index == 1
stackView.addArrangedSubview(conditionButton)
}
case .hourly:
for index in 0..<location.hourly.count {
let conditionButton = PeriodForecastButton()
conditionButton.configure(hourlyWeather: location.hourly[index])
conditionButton.index = index
conditionButton.addTarget(self, action: #selector(handleConditionButton(button:)), for: .touchUpInside)
conditionButton.isSelected = index == 1
stackView.addArrangedSubview(conditionButton)
}
}
stackView.layoutIfNeeded()
graphIsDrawn = false
drawGraphIfNeeded()
} }
@objc private func handleSegmentDidChange() { @objc private func handleSegmentDidChange() {
...@@ -142,41 +62,4 @@ private extension ForecastTimePeriodCell { ...@@ -142,41 +62,4 @@ private extension ForecastTimePeriodCell {
make.height.equalTo(172) make.height.equalTo(172)
} }
} }
func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.clipsToBounds = false
contentView.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.right.equalToSuperview()
make.top.equalTo(periodSegmentedControl.snp.bottom).offset(20).priority(.medium)
make.bottom.equalToSuperview().inset(15)
make.height.equalTo(267)
}
}
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 10
stackView.clipsToBounds = false
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 6, bottom: 0, right: 6)
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.height.equalToSuperview()
}
}
func prepareGraphView() {
//Graph view
graphView.frame = .zero
graphView.backgroundColor = .clear
scrollView.addSubview(graphView)
}
} }
...@@ -7,43 +7,22 @@ ...@@ -7,43 +7,22 @@
import UIKit import UIKit
private struct DailyGraphPoints {
let maxTempPoints: [CGPoint]
let minTempPoints: [CGPoint]
}
private struct HourlyGraphPoints {
let points: [CGPoint]
}
class CityForecastTimePeriodCell: UITableViewCell { class CityForecastTimePeriodCell: UITableViewCell {
//Private //Private
private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(), private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(),
"forecast.timePeriod.hourly".localized()]) "forecast.timePeriod.hourly".localized()])
private let kMinGraphHeight:CGFloat = 20 private let forecastTimePeriodView = ForecastTimePeriodView()
private let scrollView = UIScrollView()
private let stackView = UIStackView()
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 var location:Location?
private var currentTimePeriod = TimePeriod.daily
private var dailyGraphPoints: DailyGraphPoints?
private var hourlyGraphPoints: HourlyGraphPoints?
private let graphView = GraphView()
private var graphIsDrawn = false
//MARK:- Cell life cycle //MARK:- Cell life cycle
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)
prepareCell() prepareCell()
prepareSegmentedControl() prepareSegmentedControl()
prepareScrollView() prepareForecastTimePeriodView()
prepareStackView()
prepareGraphView()
prepareSummaryView() prepareSummaryView()
} }
...@@ -53,215 +32,16 @@ class CityForecastTimePeriodCell: UITableViewCell { ...@@ -53,215 +32,16 @@ class CityForecastTimePeriodCell: UITableViewCell {
//Public //Public
public func configure(with location:Location) { public func configure(with location:Location) {
self.location = location let forecastTimePeriod = ForecastTimePeriod(daily: location.daily, hourly: location.hourly)
rebuildStackButtons() self.forecastTimePeriodView.set(forecastTimePeriod: forecastTimePeriod)
}
public func drawGraphIfNeeded() {
guard graphIsDrawn == false else { return }
//Checking for correct height
guard
let periodButton = stackView.arrangedSubviews.first as? PeriodForecastButton,
periodButton.graphRect.height >= kMinGraphHeight
else {
return
}
graphView.frame = .init(x: 0,
y: periodButton.graphRect.origin.y,
width: stackView.frame.width,
height: periodButton.graphRect.height)
updateGraphPoints()
drawGraph()
graphIsDrawn = true
}
//Private
private func rebuildStackButtons() {
//Update graph buttons
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
guard let location = self.location else { return }
switch currentTimePeriod {
case .daily:
for index in 0..<location.daily.count {
let conditionButton = PeriodForecastButton()
conditionButton.configure(dailyWeather: location.daily[index])
conditionButton.index = index
conditionButton.addTarget(self, action: #selector(handleConditionButton(button:)), for: .touchUpInside)
conditionButton.isSelected = index == 1
stackView.addArrangedSubview(conditionButton)
}
case .hourly:
for index in 0..<location.hourly.count {
let conditionButton = PeriodForecastButton()
conditionButton.configure(hourlyWeather: location.hourly[index])
conditionButton.index = index
conditionButton.addTarget(self, action: #selector(handleConditionButton(button:)), for: .touchUpInside)
conditionButton.isSelected = index == 1
stackView.addArrangedSubview(conditionButton)
}
}
stackView.layoutIfNeeded()
graphIsDrawn = false
drawGraphIfNeeded()
}
private func drawGraph() {
guard
let periodButtons = stackView.arrangedSubviews as? [PeriodForecastButton],
let selectedButton = (periodButtons.first{ $0.isSelected })
else {
return
}
switch currentTimePeriod {
case .daily:
guard
let maxTempPoints = dailyGraphPoints?.maxTempPoints,
let minTempsPoints = dailyGraphPoints?.minTempPoints
else {
return
}
self.graphView.drawMainGraph(with: maxTempPoints)
self.graphView.drawAdditionalGraph(with: minTempsPoints)
self.tintGraphAt(button: selectedButton)
case .hourly:
guard let hourlyPoints = hourlyGraphPoints?.points else { return }
self.graphView.drawMainGraph(with: hourlyPoints)
self.graphView.drawAdditionalGraph(with: [CGPoint]())
self.tintGraphAt(button: selectedButton)
}
print("Draw graph!")
}
private func tintGraphAt(button:PeriodForecastButton) {
switch currentTimePeriod {
case .daily:
guard
let maxTempPoints = dailyGraphPoints?.maxTempPoints,
let minTempsPoints = dailyGraphPoints?.minTempPoints
else {
return
}
self.graphView.tintGraphFrom(startPointX: button.frame.origin.x,
endPointX: button.frame.origin.x + button.bounds.width)
self.graphView.tintMainDotAt(point: maxTempPoints[button.index])
self.graphView.tintAdditionalDotAt(point: minTempsPoints[button.index])
case .hourly:
guard let points = hourlyGraphPoints?.points else { return }
self.graphView.tintGraphFrom(startPointX: button.frame.origin.x,
endPointX: button.frame.origin.x + button.bounds.width)
self.graphView.tintMainDotAt(point: points[button.index])
}
}
private func updateGraphPoints(){
switch currentTimePeriod {
case .daily:
self.updateDailyGraphPoints()
case .hourly:
self.updateHourlyGraphPoints()
}
}
private func updateDailyGraphPoints() {
guard
let daysCount = location?.daily.count,
let maxTemps = (location?.daily.map{ CGFloat($0.maxTemp?.localeValue ?? 0) }),
let topMaxTemp = maxTemps.max(),
let minTemps = (location?.daily.map{ CGFloat($0.minTemp?.localeValue ?? 0) }),
let topMinTemp = minTemps.max()
else {
return
}
var maxPoints = [CGPoint]()
var minPoints = [CGPoint]()
for index in 0..<daysCount {
guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue }
let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width
let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2
let maxTempLevelsCount = CGFloat(Set(maxTemps).count)
let minTempLevelsCount = CGFloat(Set(minTemps).count)
let totalLevels = maxTempLevelsCount + minTempLevelsCount
let levelHeight = (graphView.frame.height / CGFloat(totalLevels)).rounded(.down)
let maxTempFrame = CGRect(x: 0, y: 0, width: graphView.frame.width, height: maxTempLevelsCount * levelHeight)
let minTempFrame = CGRect(x: 0, y: maxTempFrame.height, width: graphView.frame.width, height: minTempLevelsCount * levelHeight)
//Max
var maxPointLevel = maxTempFrame.origin.y + ((topMaxTemp - maxTemps[index]) * levelHeight) + levelHeight
maxPointLevel = min(maxPointLevel, maxTempFrame.height + 5)
//Min
var minPointLevel = minTempFrame.origin.y + ((topMinTemp - minTemps[index]) * levelHeight) + levelHeight
minPointLevel = min(minPointLevel, minTempFrame.height + minTempFrame.origin.y - 5)
maxPoints.append(.init(x: buttonCenterX, y: maxPointLevel))
minPoints.append(.init(x: buttonCenterX, y: minPointLevel))
}
dailyGraphPoints = DailyGraphPoints(maxTempPoints: maxPoints, minTempPoints: minPoints)
} }
private func updateHourlyGraphPoints() { @objc private func handleSegmentDidChange() {
guard guard let timePeriod = TimePeriod(rawValue: self.periodSegmentedControl.selectedSegmentIndex) else {
let hoursCount = location?.hourly.count,
let temps = (location?.hourly.map{ CGFloat($0.temp?.localeValue ?? 0) }),
let maxTemp = temps.max()
else {
return return
} }
var points = [CGPoint]() self.forecastTimePeriodView.set(timePeriod: timePeriod)
for index in 0..<hoursCount {
guard let stackButton = stackView.arrangedSubviews[index] as? PeriodForecastButton else { continue }
let buttonRightSide = stackButton.frame.origin.x + stackButton.bounds.width
let buttonCenterX = (buttonRightSide + stackButton.frame.origin.x) / 2
let levelsCount = CGFloat(Set(temps).count)
let levelHeight = (graphView.frame.height / CGFloat(levelsCount)).rounded(.down)
let tempFrame = CGRect(x: 0, y: 0, width: graphView.frame.width, height: graphView.frame.height)
var pointLevel = tempFrame.origin.y + ((maxTemp - temps[index]) * levelHeight)
pointLevel = max(pointLevel, tempFrame.origin.y + 10)
pointLevel = min(tempFrame.height - 10, pointLevel)
points.append(.init(x: buttonCenterX, y: pointLevel))
}
hourlyGraphPoints = HourlyGraphPoints(points: points)
}
@objc private func handleConditionButton(button: PeriodForecastButton) {
stackView.arrangedSubviews.forEach {
if let periodButton = $0 as? PeriodForecastButton {
periodButton.isSelected = periodButton === button
if periodButton.isSelected {
tintGraphAt(button: periodButton)
}
}
}
}
@objc private func handleSegmentDidChange() {
self.currentTimePeriod = TimePeriod(rawValue: self.periodSegmentedControl.selectedSegmentIndex)!
self.rebuildStackButtons()
} }
} }
...@@ -283,13 +63,9 @@ private extension CityForecastTimePeriodCell { ...@@ -283,13 +63,9 @@ private extension CityForecastTimePeriodCell {
} }
} }
func prepareScrollView() { func prepareForecastTimePeriodView() {
scrollView.showsVerticalScrollIndicator = false contentView.addSubview(forecastTimePeriodView)
scrollView.showsHorizontalScrollIndicator = false forecastTimePeriodView.snp.makeConstraints { (make) in
scrollView.clipsToBounds = false
contentView.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.left.equalToSuperview() make.left.equalToSuperview()
make.right.equalToSuperview() make.right.equalToSuperview()
make.top.equalTo(periodSegmentedControl.snp.bottom).offset(30).priority(.medium) make.top.equalTo(periodSegmentedControl.snp.bottom).offset(30).priority(.medium)
...@@ -297,28 +73,6 @@ private extension CityForecastTimePeriodCell { ...@@ -297,28 +73,6 @@ private extension CityForecastTimePeriodCell {
} }
} }
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 10
stackView.clipsToBounds = false
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 6, bottom: 0, right: 6)
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.height.equalToSuperview()
}
}
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")
...@@ -348,7 +102,7 @@ private extension CityForecastTimePeriodCell { ...@@ -348,7 +102,7 @@ private extension CityForecastTimePeriodCell {
summaryView.snp.makeConstraints { (make) in summaryView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18) make.left.right.equalToSuperview().inset(18)
make.height.equalTo(40) make.height.equalTo(40)
make.top.equalTo(scrollView.snp.bottom).offset(20) make.top.equalTo(forecastTimePeriodView.snp.bottom).offset(20)
make.bottom.equalToSuperview().inset(15) make.bottom.equalToSuperview().inset(15)
} }
} }
......
...@@ -83,7 +83,8 @@ class TodayCellFactory { ...@@ -83,7 +83,8 @@ class TodayCellFactory {
public func willDisplay(cell:UITableViewCell) { public func willDisplay(cell:UITableViewCell) {
switch cell { switch cell {
case let timePeriodCell as CityForecastTimePeriodCell: case let timePeriodCell as CityForecastTimePeriodCell:
timePeriodCell.drawGraphIfNeeded() break
// timePeriodCell.drawGraphIfNeeded()
case let sunCell as CitySunCell: case let sunCell as CitySunCell:
sunCell.updateSunPosition() sunCell.updateSunPosition()
case let moonCell as CityMoonCell: case let moonCell as CityMoonCell:
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<key>XMLCoder.xcscheme_^#shared#^_</key> <key>XMLCoder.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>6</integer> <integer>5</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>
......
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