Commit 42c32561 by Dmitriy Stepanets

Working on hourly graph view

parent ba2e8e60
......@@ -21,6 +21,8 @@
CD39F2EE25DE858D009FE398 /* NotificationName+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2ED25DE858D009FE398 /* NotificationName+Localization.swift */; };
CD39F2F225DE94C4009FE398 /* CitySunCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2F125DE94C4009FE398 /* CitySunCell.swift */; };
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2F425DE9571009FE398 /* ArrowButton.swift */; };
CD647D0225ED07D60034578B /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD647D0125ED07D60034578B /* TodayViewModel.swift */; };
CD647D0625ED08050034578B /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD647D0525ED08050034578B /* ViewModelProtocol.swift */; };
CD6B3036257262C2004B34B3 /* UIColor+Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B3035257262C2004B34B3 /* UIColor+Highlight.swift */; };
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; };
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; };
......@@ -39,6 +41,9 @@
CD86246C25E6826A0097F3FB /* InnerShadowLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD86246B25E6826A0097F3FB /* InnerShadowLayer.swift */; };
CD9B6B1125DBC723001D9B80 /* CubicCurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9B6B1025DBC723001D9B80 /* CubicCurveAlgorithm.swift */; };
CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9B6B1325DBCDE2001D9B80 /* GraphView.swift */; };
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA5542725EF734200A2E08C /* TodayCellFactory.swift */; };
CDA5542D25EF7C9700A2E08C /* ReusableCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA5542C25EF7C9700A2E08C /* ReusableCellProtocol.swift */; };
CDA5543025EFA13F00A2E08C /* Measurement+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA5542F25EFA13F00A2E08C /* Measurement+String.swift */; };
CDC6124F25E7964700188DA7 /* CityDayTimesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC6124E25E7964700188DA7 /* CityDayTimesCell.swift */; };
CDC6125325E79C8F00188DA7 /* DayTimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC6125225E79C8F00188DA7 /* DayTimeView.swift */; };
CDC6125725E7AB1A00188DA7 /* CityAirQualityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC6125625E7AB1A00188DA7 /* CityAirQualityCell.swift */; };
......@@ -97,6 +102,8 @@
CD39F2ED25DE858D009FE398 /* NotificationName+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationName+Localization.swift"; sourceTree = "<group>"; };
CD39F2F125DE94C4009FE398 /* CitySunCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CitySunCell.swift; sourceTree = "<group>"; };
CD39F2F425DE9571009FE398 /* ArrowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowButton.swift; sourceTree = "<group>"; };
CD647D0125ED07D60034578B /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = "<group>"; };
CD647D0525ED08050034578B /* ViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = "<group>"; };
CD6B3035257262C2004B34B3 /* UIColor+Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Highlight.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>"; };
......@@ -115,6 +122,9 @@
CD86246B25E6826A0097F3FB /* InnerShadowLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerShadowLayer.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>"; };
CDA5542725EF734200A2E08C /* TodayCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayCellFactory.swift; sourceTree = "<group>"; };
CDA5542C25EF7C9700A2E08C /* ReusableCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableCellProtocol.swift; sourceTree = "<group>"; };
CDA5542F25EFA13F00A2E08C /* Measurement+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Measurement+String.swift"; sourceTree = "<group>"; };
CDC6124E25E7964700188DA7 /* CityDayTimesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityDayTimesCell.swift; sourceTree = "<group>"; };
CDC6125225E79C8F00188DA7 /* DayTimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayTimeView.swift; sourceTree = "<group>"; };
CDC6125625E7AB1A00188DA7 /* CityAirQualityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityAirQualityCell.swift; sourceTree = "<group>"; };
......@@ -201,6 +211,7 @@
CE9D181425ECB8370028D9D7 /* Common */,
CEAFF09925DFC78200DF4EBF /* Network */,
CEAFF08125DFC66F00DF4EBF /* Model */,
CD647D0025ED07AF0034578B /* ViewModels */,
CD17C5F925D15B5500EE884E /* Coordinators */,
CDD0F1DC2572400200CF5017 /* UI */,
CD1237EF255D838600C98139 /* Extensions */,
......@@ -220,6 +231,7 @@
CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */,
CDE18DC925D165F100C80ED9 /* UITabBarController+Append.swift */,
CD39F2ED25DE858D009FE398 /* NotificationName+Localization.swift */,
CDA5542F25EFA13F00A2E08C /* Measurement+String.swift */,
);
path = Extensions;
sourceTree = "<group>";
......@@ -252,6 +264,15 @@
path = Coordinators;
sourceTree = "<group>";
};
CD647D0025ED07AF0034578B /* ViewModels */ = {
isa = PBXGroup;
children = (
CD647D0125ED07D60034578B /* TodayViewModel.swift */,
CD647D0525ED08050034578B /* ViewModelProtocol.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
CD6B3038257267E2004B34B3 /* View controllers */ = {
isa = PBXGroup;
children = (
......@@ -284,6 +305,7 @@
CD822FF325D6814300A05501 /* Cells */ = {
isa = PBXGroup;
children = (
CDA5542725EF734200A2E08C /* TodayCellFactory.swift */,
CD82300125D69DB900A05501 /* CityConditions */,
CDEE8AD425DA87DD00C289DE /* CityForecastTimePeriod */,
CD86246325E66E6B0097F3FB /* CityPrecipCell */,
......@@ -418,6 +440,7 @@
children = (
CE9D181825ECB9A70028D9D7 /* Logger.swift */,
CE9D181525ECB8370028D9D7 /* MulticastDelegate.swift */,
CDA5542C25EF7C9700A2E08C /* ReusableCellProtocol.swift */,
);
path = Common;
sourceTree = "<group>";
......@@ -537,6 +560,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = CD1237B6255D5C5900C98139;
productRefGroup = CD1237C0255D5C5900C98139 /* Products */;
......@@ -612,6 +636,7 @@
CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */,
CEC526FD25E795F700DA58A5 /* WdtWeatherSource.swift in Sources */,
CEAFF09225DFC71D00DF4EBF /* HelperTypes.swift in Sources */,
CDA5543025EFA13F00A2E08C /* Measurement+String.swift in Sources */,
CE9D181925ECB9A70028D9D7 /* Logger.swift in Sources */,
CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */,
CEC5270325E7BB4000DA58A5 /* WdtSurfaceObservation.swift in Sources */,
......@@ -632,6 +657,7 @@
CEAFF08F25DFC6ED00DF4EBF /* HourlyWeather.swift in Sources */,
CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */,
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */,
CEDE4E8525EEFD56007457E9 /* WdtHourlySummariesArray.swift in Sources */,
......@@ -652,6 +678,7 @@
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */,
CD9B6B1125DBC723001D9B80 /* CubicCurveAlgorithm.swift in Sources */,
CEC5270025E7BACB00DA58A5 /* WdtLocation.swift in Sources */,
CD647D0225ED07D60034578B /* TodayViewModel.swift in Sources */,
CD15DB4225DA806C00024727 /* CityForecastTimePeriodCell.swift in Sources */,
CEC5276025E92DDA00DA58A5 /* WdtHourlySummary.swift in Sources */,
CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */,
......@@ -669,6 +696,8 @@
CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */,
CD82300725D6A73F00A05501 /* CityConditionButton.swift in Sources */,
CDC6126A25E90C8800188DA7 /* GraphLineSettings.swift in Sources */,
CDA5542D25EF7C9700A2E08C /* ReusableCellProtocol.swift in Sources */,
CD647D0625ED08050034578B /* ViewModelProtocol.swift in Sources */,
CD86246C25E6826A0097F3FB /* InnerShadowLayer.swift in Sources */,
CEAFF0A325E0FF0800DF4EBF /* LocationManager.swift in Sources */,
CEAD00A12577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift in Sources */,
......
//
// ReusableCellProtocol.swift
// 1Weather
//
// Created by Dmitry Stepanets on 03.03.2021.
//
import UIKit
public protocol ReusableCellProtocol: class {
static var kIdentifier:String { get }
}
extension UITableViewCell: ReusableCellProtocol {
public static var kIdentifier: String {
let identifier = String(describing: self)
if identifier == "UITableViewCell" {
assertionFailure("Invalid reuse identifier name. Please provide a valid name.")
}
return String(describing: self)
}
}
......@@ -9,6 +9,7 @@ import UIKit
class TodayCoordinator:Coordinator {
//Private
private let todayViewModel = TodayViewModel()
private let navigationController = UINavigationController(nibName: nil, bundle: nil)
private var tabBarController:UITabBarController?
......@@ -21,7 +22,7 @@ class TodayCoordinator:Coordinator {
}
func start() {
let todayViewController = TodayViewController()
let todayViewController = TodayViewController(viewModel: todayViewModel)
navigationController.viewControllers = [todayViewController]
tabBarController?.add(viewController: navigationController)
}
......
//
// Measurement+String.swift
// 1Weather
//
// Created by Dmitry Stepanets on 03.03.2021.
//
import Foundation
extension Measurement {
private static var formatter:MeasurementFormatter {
let fmt = MeasurementFormatter()
fmt.unitStyle = .medium
fmt.numberFormatter.maximumFractionDigits = 0
return fmt
}
private static var shortFormatter:MeasurementFormatter {
let fmt = MeasurementFormatter()
fmt.unitStyle = .short
fmt.numberFormatter.maximumFractionDigits = 0
return fmt
}
var string:String {
return Measurement.formatter.string(from: self)
}
var shortString:String {
return Measurement.shortFormatter.string(from: self)
}
}
extension Temperature {
var localeValue:Double {
return self.converted(to: .fahrenheit).value.rounded(.down)
}
}
......@@ -20,6 +20,10 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIAppFonts</key>
<array>
<string>SF-Pro.ttf</string>
</array>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......@@ -31,8 +35,6 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
......@@ -41,9 +43,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIAppFonts</key>
<array>
<string>SF-Pro.ttf</string>
</array>
</dict>
</plist>
......@@ -8,7 +8,7 @@
import Foundation
public enum WeatherType: String {
public enum WeatherType: String, CaseIterable {
case clearDay = "clearDay"
case partlyCloudyDay = "partlyCloudyDay"
case snowyDay = "snowyDay"
......@@ -30,9 +30,56 @@ public enum WeatherType: String {
case heavyRain = "heavyRain"
case smoke = "smoke"
case unknown = "unknown"
var localized:String {
switch self {
case .clearDay:
return "forecast.clear".localized()
case .partlyCloudyDay:
return "forecast.cloudy".localized()
case .snowyDay:
return "forecast.snowyDay".localized()
case .snowyNight:
return "forecast.snowyNight".localized()
case .clearNight:
return "forecast.clearNight".localized()
case .partlyCloudyNight:
return "forecast.partlyCloudyNight".localized()
case .thunderstorm:
return "forecast.thunderstorm".localized()
case .heavySnow:
return "forecast.heavySnow".localized()
case .lightSnow:
return "forecast.lightSnow".localized()
case .freezingRain:
return "forecast.freezingRain".localized()
case .freezingFog:
return "forecast.freezingFog".localized()
case .lightHail:
return "forecast.lightHail".localized()
case .blowingDust:
return "forecast.blowingDust".localized()
case .haze:
return "forecast.haze".localized()
case .lightFog:
return "forecast.lightFog".localized()
case .mostlyCloudyDay:
return "forecast.mostlyCloudyDay".localized()
case .mostlyCloudyNight:
return "forecast.mostlyCloudyNight".localized()
case .lightDrizzle:
return "forecast.lightDrizzle".localized()
case .heavyRain:
return "forecast.heavyRain".localized()
case .smoke:
return "forecast.smoke".localized()
case .unknown:
return "forecast.unknown".localized()
}
}
}
public enum WindDirection: String, Codable {
public enum WindDirection: String, Codable, CaseIterable {
case north = "N"
case northNorthEast = "NNE"
case northEast = "NE"
......@@ -84,7 +131,6 @@ public enum MoonPhase: String, Codable {
}
public struct Time: CustomStringConvertible, Codable {
public let hours: UInt8
public let minutes: UInt8
......@@ -102,6 +148,11 @@ public struct Time: CustomStringConvertible, Codable {
return "\(String(format: "%02d", convertedHours)):\(minutes) \(amOrPm)"
}
public init(hours: UInt8, minutes: UInt8) {
self.hours = hours
self.minutes = minutes
}
public init(from decoder: Decoder) throws {
let valueContainer = try decoder.singleValueContainer()
let value = try valueContainer.decode(String.self).trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
......
......@@ -10,6 +10,25 @@
"forecast.clear" = "Clear";
"forecast.cloudy" = "Cloudy";
"forecast.partyCloudy" = "Partly Cloudy";
"forecast.snowyDay" = "Snowy Day";
"forecast.snowyNight" = "Snowy Night";
"forecast.clearNight" = "Clear Night";
"forecast.partlyCloudyNight" = "Partly Cloudy Night";
"forecast.thunderstorm" = "Thunderstorm";
"forecast.heavySnow" = "Heavy Snow";
"forecast.lightSnow" = "Light Snow";
"forecast.freezingRain" = "Freezing Rain";
"forecast.freezingFog" = "Freezing Fog";
"forecast.lightHail" = "Light Hail";
"forecast.blowingDust" = "Blowing Dust";
"forecast.haze" = "Haze";
"forecast.lightFog" = "Light Fog";
"forecast.mostlyCloudyDay" = "Mostly Cloudy Day";
"forecast.mostlyCloudyNight" = "Mostly Cloudy Night";
"forecast.lightDrizzle" = "Light Drizzle";
"forecast.heavyRain" = "Heavy Rain";
"forecast.smoke" = "Smoke";
"forecast.unknown" = "Unknown";
//Day time
"dayTime.morning" = "Morning";
......@@ -24,6 +43,7 @@
"condition.pressure" = "Pressure";
"condition.dewPoint" = "Dew point";
"condition.visibility" = "Visibility";
"condition.wind" = "Wind";
//Forecast time period
"forecast.timePeriod.daily" = "Daily";
......
......@@ -19,6 +19,9 @@ class CubicCurveAlgorithm
private var secondControlPoints: [CGPoint?] = []
func controlPointsFromPoints(dataPoints: [CGPoint]) -> [CubicCurveSegment] {
//Clean up
firstControlPoints.removeAll()
secondControlPoints.removeAll()
//Number of Segments
let count = dataPoints.count - 1
......
......@@ -15,7 +15,6 @@ struct LineDot {
struct GraphLine {
//Private
private let cubicCurveAlgorithm = CubicCurveAlgorithm()
private let kIntersectAccuracy:CGFloat = 2
private var points = [CGPoint]()
private let settings:GraphLineSettings
......@@ -70,6 +69,7 @@ struct GraphLine {
return path
}
let cubicCurveAlgorithm = CubicCurveAlgorithm()
let controlPoints = cubicCurveAlgorithm.controlPointsFromPoints(dataPoints: pointsToAdd)
for index in 1..<pointsToAdd.count {
sections.append(.init(p0: pointsToAdd[index - 1],
......@@ -151,8 +151,12 @@ struct GraphLine {
self.sections.removeAll()
self.lineDots.removeAll()
self.points = points
self.tintLineShape.path = nil
guard !self.points.isEmpty else { return }
guard !self.points.isEmpty else {
lineShape.path = nil
return
}
//Add new dots
self.points.forEach {
......
......@@ -8,8 +8,6 @@
import UIKit
class CityAirQualityCell: UITableViewCell {
static let kIdentifier = "cityAirQualityCell"
//Private
private let headingLabel = UILabel()
private let valueCircle = CAShapeLayer()
......
......@@ -14,16 +14,19 @@ enum CityConditionButtonType: CaseIterable {
case pressure
case dewPoint
case visibility
case wind
}
class CityConditionButton: UIControl {
//Private
private let buttonType:CityConditionButtonType
private let imageView = UIImageView()
private let valueLabel = UILabel()
private let nameLabel = UILabel()
private let descriptionLabel = UILabel()
init(type: CityConditionButtonType) {
self.buttonType = type
super.init(frame: .zero)
prepareButton()
......@@ -31,6 +34,27 @@ class CityConditionButton: UIControl {
prepareLabels(type: type)
}
public func configure(with location:Location) {
switch buttonType {
case .precipitation:
valueLabel.text = "\(location.today?.precipitationProbability ?? 0)%"
case .humidity:
valueLabel.text = "\(location.today?.humidity ?? 0)%"
case .uvIndex:
valueLabel.text = "4"
case .pressure:
valueLabel.text = location.today?.pressure?.string
case .dewPoint:
valueLabel.text = "12°"
case .visibility:
valueLabel.text = location.today?.visibility?.string
case .wind:
let speed = location.today?.windSpeed?.string ?? "--"
let direction = location.today?.windDirection?.rawValue ?? ""
valueLabel.text = "\(direction) \(speed)"
}
}
private func image(for type:CityConditionButtonType) -> UIImage? {
switch type {
case .precipitation:
......@@ -45,6 +69,8 @@ class CityConditionButton: UIControl {
return UIImage(named: "dewPoint")
case .visibility:
return UIImage(named: "visibility")
case .wind:
return UIImage(named: "blowingDust")
}
}
......@@ -62,6 +88,8 @@ class CityConditionButton: UIControl {
return "condition.dewPoint".localized()
case .visibility:
return "condition.visibility".localized()
case .wind:
return "condition.wind".localized()
}
}
......
......@@ -9,8 +9,6 @@ import UIKit
import SnapKit
class CityConditionsCell: UITableViewCell {
static let kIdentifier = "cityConditionsCell"
//Private
private let kIndicatorWidth:CGFloat = 27
private let scrollView = UIScrollView()
......@@ -29,6 +27,14 @@ class CityConditionsCell: UITableViewCell {
prepareProgress()
}
public func configure(with location:Location) {
stackView.arrangedSubviews.forEach {
if let conditionButton = $0 as? CityConditionButton {
conditionButton.configure(with: location)
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
......
......@@ -8,8 +8,6 @@
import UIKit
class CityDayTimesCell: UITableViewCell {
static let kIdentifier = "cityDayTimesCell"
//Private
private let headingLabel = UILabel()
private let headingButton = ArrowButton()
......
......@@ -9,8 +9,6 @@ import UIKit
import SnapKit
class CityForecastCell: UITableViewCell {
static let kIdentifier = "cityForecastCell"
//Private
private let container = UIView()
private let cityImageView = UIImageView()
......@@ -34,6 +32,17 @@ class CityForecastCell: UITableViewCell {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func configure(with location:Location) {
cityImageView.image = UIImage(named: location.imageName)
temperatureLabel.text = location.today?.temp?.shortString
let maxTemp = location.today?.maxTemp?.shortString ?? "--"
let minTemp = location.today?.minTemp?.shortString ?? "--"
let feelstemp = location.today?.apparentTemp?.shortString ?? "--"
forecastDescriptionLabel.text = "\(location.today?.type.localized ?? "") | \(maxTemp)/\(minTemp)"
feelsLikeLabel.text = "Feels like \(feelstemp) - Due to high humidity"
forecastImageView.image = UIImage(named: location.today?.type.rawValue ?? "")
}
}
//MARK:- Prepare
......
......@@ -70,9 +70,14 @@ class PeriodForecastButton: UIControl {
}
//Public
public func configure(dayTemp:DayTemp) {
self.tempLabel.text = "\(Int(dayTemp.max))°"
self.minTempLabel.text = "\(Int(dayTemp.min))°"
public func configure(dailyWeather: DailyWeather) {
self.tempLabel.text = dailyWeather.maxTemp?.shortString
self.minTempLabel.text = dailyWeather.minTemp?.shortString
}
public func configure(hourlyWeather: HourlyWeather) {
self.tempLabel.text = hourlyWeather.temp?.shortString
self.minTempLabel.text = nil
}
}
......
......@@ -8,8 +8,6 @@
import UIKit
class CityMoonCell: UITableViewCell {
static let kIdentifier = "cityMoonCell"
//Private
private let kCircleInset:CGFloat = 55
private let headingLabel = UILabel()
......
......@@ -8,8 +8,6 @@
import UIKit
class CityPrecipCell: UITableViewCell {
static let kIdentifier = "cityPrecipCell"
//Private
private let headingLabel = UILabel()
private let headingButton = ArrowButton()
......
......@@ -27,7 +27,6 @@ private struct CircleSegment {
}
class CitySunCell: UITableViewCell {
static let kIdentifier = "citySunCell"
//Private
//Heading
private let headingLabel = UILabel()
......
......@@ -8,8 +8,6 @@
import UIKit
class TodayAdCell: UITableViewCell {
static let kIdentifier = "todayAdCell"
//Private
private let container = UIView()
private let gradientView = GradientView(startColor: UIColor.white.withAlphaComponent(0),
......
//
// TodayCellFactory.swift
// 1Weather
//
// Created by Dmitry Stepanets on 03.03.2021.
//
import UIKit
private enum TodayCellType:Int, CaseIterable {
case forecast = 0
case ad
case conditions
case forecastPeriod
case precipitation
case dayTime
case sun
case moon
}
class TodayCellFactory {
public var onGetLocation:(() -> Location?)?
public var numberOfRows:Int {
return TodayCellType.allCases.count
}
public func registerCells(on tableView:UITableView) {
registerCell(type: CityForecastCell.self, tableView: tableView)
registerCell(type: TodayAdCell.self, tableView: tableView)
registerCell(type: CityConditionsCell.self, tableView: tableView)
registerCell(type: CityForecastTimePeriodCell.self, tableView: tableView)
registerCell(type: CityPrecipCell.self, tableView: tableView)
registerCell(type: CityDayTimesCell.self, tableView: tableView)
registerCell(type: CityAirQualityCell.self, tableView: tableView)
registerCell(type: CitySunCell.self, tableView: tableView)
registerCell(type: CityMoonCell.self, tableView: tableView)
}
public func cellFromTableView(tableView:UITableView, indexPath:IndexPath) -> UITableViewCell {
guard let cellType = TodayCellType(rawValue: indexPath.row) else {
return UITableViewCell()
}
switch cellType {
case .forecast:
let cell = dequeueReusableCell(type: CityForecastCell.self, tableView: tableView, indexPath: indexPath)
if let loc = self.onGetLocation?() {
cell.configure(with: loc)
}
return cell
case .ad:
let cell = dequeueReusableCell(type: TodayAdCell.self, tableView: tableView, indexPath: indexPath)
return cell
case .conditions:
let cell = dequeueReusableCell(type: CityConditionsCell.self, tableView: tableView, indexPath: indexPath)
if let loc = self.onGetLocation?() {
cell.configure(with: loc)
}
return cell
case .forecastPeriod:
let cell = dequeueReusableCell(type: CityForecastTimePeriodCell.self, tableView: tableView, indexPath: indexPath)
if let loc = self.onGetLocation?() {
cell.configure(with: loc)
}
return cell
case .precipitation:
let cell = dequeueReusableCell(type: CityPrecipCell.self, tableView: tableView, indexPath: indexPath)
return cell
case .dayTime:
let cell = dequeueReusableCell(type: CityDayTimesCell.self, tableView: tableView, indexPath: indexPath)
return cell
case .sun:
let cell = dequeueReusableCell(type: CitySunCell.self, tableView: tableView, indexPath: indexPath)
return cell
case .moon:
let cell = dequeueReusableCell(type: CityMoonCell.self, tableView: tableView, indexPath: indexPath)
return cell
}
}
public func willDisplay(cell:UITableViewCell) {
switch cell {
case let timePeriodCell as CityForecastTimePeriodCell:
timePeriodCell.drawGraphIfNeeded()
case let sunCell as CitySunCell:
sunCell.updateSunPosition()
case let moonCell as CityMoonCell:
moonCell.updateMoonPosition()
default:
break
}
}
//Private
private func registerCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView) {
tableView.register(type, forCellReuseIdentifier: T.kIdentifier)
}
private func dequeueReusableCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView, indexPath: IndexPath) -> T {
let cell = tableView.dequeueReusableCell(withIdentifier: T.kIdentifier, for: indexPath) as! T
return cell
}
}
......@@ -8,21 +8,10 @@
import UIKit
import CoreLocation
private enum TodayTableCell {
case forecast
case ad
case conditions
case forecastPeriod
case precipitation
case dayTime
case sun
case moon
}
class TodayViewController: UIViewController {
//Private
private let viewModel:TodayViewModel
private let tableView = UITableView()
private let tableCells:[TodayTableCell] = [.forecast, .ad, .conditions, .forecastPeriod, .precipitation, .dayTime, .sun, .moon]
private var localizationObserver:Any?
deinit {
......@@ -30,6 +19,16 @@ class TodayViewController: UIViewController {
NotificationCenter.default.removeObserver(observer)
}
}
init(viewModel: TodayViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
......@@ -87,15 +86,7 @@ private extension TodayViewController {
}
func prepareTableView() {
tableView.register(CityForecastCell.self, forCellReuseIdentifier: CityForecastCell.kIdentifier)
tableView.register(TodayAdCell.self, forCellReuseIdentifier: TodayAdCell.kIdentifier)
tableView.register(CityConditionsCell.self, forCellReuseIdentifier: CityConditionsCell.kIdentifier)
tableView.register(CityForecastTimePeriodCell.self, forCellReuseIdentifier: CityForecastTimePeriodCell.kIdentifier)
tableView.register(CityPrecipCell.self, forCellReuseIdentifier: CityPrecipCell.kIdentifier)
tableView.register(CityDayTimesCell.self, forCellReuseIdentifier: CityDayTimesCell.kIdentifier)
tableView.register(CityAirQualityCell.self, forCellReuseIdentifier: CityAirQualityCell.kIdentifier)
tableView.register(CitySunCell.self, forCellReuseIdentifier: CitySunCell.kIdentifier)
tableView.register(CityMoonCell.self, forCellReuseIdentifier: CityMoonCell.kIdentifier)
viewModel.todayCellFactory.registerCells(on: tableView)
tableView.contentInset = .init(top: 0, left: 0, bottom: 15, right: 0)
tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
tableView.separatorStyle = .none
......@@ -115,52 +106,17 @@ private extension TodayViewController {
//MARK:- UITableView Data Source
extension TodayViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableCells.count
return viewModel.todayCellFactory.numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch self.tableCells[indexPath.row] {
case .forecast:
let cell = tableView.dequeueReusableCell(withIdentifier: CityForecastCell.kIdentifier, for: indexPath) as! CityForecastCell
return cell
case .ad:
let cell = tableView.dequeueReusableCell(withIdentifier: TodayAdCell.kIdentifier, for: indexPath)
return cell
case .conditions:
let cell = tableView.dequeueReusableCell(withIdentifier: CityConditionsCell.kIdentifier, for: indexPath)
return cell
case .forecastPeriod:
let cell = tableView.dequeueReusableCell(withIdentifier: CityForecastTimePeriodCell.kIdentifier, for: indexPath) as! CityForecastTimePeriodCell
return cell
case .precipitation:
let cell = tableView.dequeueReusableCell(withIdentifier: CityPrecipCell.kIdentifier, for: indexPath) as! CityPrecipCell
return cell
case .dayTime:
let cell = tableView.dequeueReusableCell(withIdentifier: CityDayTimesCell.kIdentifier, for: indexPath) as! CityDayTimesCell
return cell
case .sun:
let cell = tableView.dequeueReusableCell(withIdentifier: CitySunCell.kIdentifier, for: indexPath) as! CitySunCell
return cell
case .moon:
let cell = tableView.dequeueReusableCell(withIdentifier: CityMoonCell.kIdentifier, for: indexPath) as! CityMoonCell
return cell
}
return viewModel.todayCellFactory.cellFromTableView(tableView: tableView, indexPath: indexPath)
}
}
//MARK:- UITableView Delegate
extension TodayViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let timePeriodCell = cell as? CityForecastTimePeriodCell {
timePeriodCell.drawGraphIfNeeded()
}
if let sunCell = cell as? CitySunCell {
sunCell.updateSunPosition()
}
if let moonCell = cell as? CityMoonCell {
moonCell.updateMoonPosition()
}
viewModel.todayCellFactory.willDisplay(cell: cell)
}
}
//
// TodayViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 01.03.2021.
//
import UIKit
class TodayViewModel {
//Public
public let todayCellFactory = TodayCellFactory()
public weak var delegate:ViewModelDelegate?
public private(set) var location:Location
init() {
func weekDay(byIndex:Int) -> WeekDay {
switch byIndex {
case 0:
return .monday
case 1:
return .tuesday
case 2:
return .wednesday
case 3:
return .thursday
case 4:
return .friday
case 5:
return .saturday
default:
return .sunday
}
}
let today = CurrentWeather(date: Date(),
timeZone: .current,
weekDay: .monday,
type: .heavyRain,
isDay: true,
minTemp: .init(value: -4, unit: .celsius),
maxTemp: .init(value: 9, unit: .celsius),
windSpeed: .init(value: 5, unit: .milesPerHour),
windDirection: .north,
precipitationProbability: .some(30),
temp: .init(value: 4, unit: .celsius),
apparentTemp: .init(value: 5, unit: .celsius),
humidity: .some(30),
visibility: .init(value: 8, unit: .miles),
pressure: .init(value: 29.7, unit: .inchesOfMercury),
sunrise: .init(hours: 6, minutes: 26),
sunset: .init(hours: 17, minutes: 46),
sunState: .alwaysUp,
moonrise: .init(hours: 20, minutes: 00),
moonset: .init(hours: 5, minutes: 30),
moonState: .normal)
var daily = [DailyWeather]()
for index in 0..<7 {
let day = DailyWeather(date: Date(),
timeZone: .current,
weekDay: weekDay(byIndex: index),
type: WeatherType.allCases.randomElement() ?? .unknown,
minTemp: .init(value: Double.random(in: -3..<5), unit: .celsius),
maxTemp: .init(value: Double.random(in: 5..<10), unit: .celsius),
windSpeed: .init(value: Double.random(in: 0..<10), unit: .milesPerHour),
windDirection: WindDirection.allCases.randomElement() ?? .east,
precipitationProbability: .some(UInt.random(in: 0..<100)),
sunrise: .init(hours: 5, minutes: 23),
sunset: .init(hours: 17, minutes: 40),
sunState: .normal,
moonrise: .init(hours: 22, minutes: 15),
moonset: .init(hours: 2, minutes: 30),
moonState: .normal)
daily.append(day)
}
var hourly = [HourlyWeather]()
var startDate = Date()
for _ in 0..<24 {
let hour = HourlyWeather(date: startDate,
timeZone: TimeZone(abbreviation: "EST") ?? .current,
weekDay: .monday,
type: WeatherType.allCases.randomElement() ?? .unknown,
isDay: true,
temp: .init(value: Double.random(in: -3..<10), unit: .celsius),
windSpeed: .init(value: Double.random(in: 0..<10), unit: .milesPerHour),
windDirection: WindDirection.allCases.randomElement() ?? .east,
precipitationProbability: .some(UInt.random(in: 0..<100)),
humidity: .some(UInt.random(in: 0..<100)))
startDate = startDate + TimeInterval(3600)
hourly.append(hour)
}
self.location = Location(coordinates: nil,
imageName: "ny_bridge",
countryCode: "us",
countryName: "United States",
region: "New York",
cityName: "New York",
nickname: "ny",
fipsCode: "36",
today: today,
daily: daily,
hourly: hourly)
//Setup factory callback
todayCellFactory.onGetLocation = {[weak self] in
return self?.location
}
}
}
//
// ViewModelProtocol.swift
// 1Weather
//
// Created by Dmitry Stepanets on 01.03.2021.
//
import UIKit
protocol ViewModelDelegate : class {
func viewModelDidChange<P: ViewModelProtocol>(model:P)
func viewModel<P: ViewModelProtocol>(model:P, errorHasOccured error:String?)
}
//Makes delegate methods optional
extension ViewModelDelegate {
func viewModelDidChange<P: ViewModelProtocol>(model:P) {}
func viewModel<P: ViewModelProtocol>(model:P, errorHasOccured error:String?) {}
}
protocol ViewModelProtocol {
}
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "19622742EBA51E823D6DAE3F8CDBFAD4"
......@@ -23,14 +23,15 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
......@@ -38,17 +39,14 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
......
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