Commit 2d426c1d by Dmitriy Stepanets

Working on action on forecast selection button

parent 445be99f
...@@ -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>6</integer> <integer>5</integer>
</dict> </dict>
<key>PG (Playground) 1.xcscheme</key> <key>PG (Playground) 1.xcscheme</key>
<dict> <dict>
......
//
// Calendar+TimeZone.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
extension Calendar {
static func timeZoneCalendar(timeZone:TimeZone) -> Self {
var cal = Calendar(identifier: current.identifier)
cal.timeZone = timeZone
return cal
}
}
//
// UIStackView+RemoveAll.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
extension UIStackView {
func removeAll() {
arrangedSubviews.forEach {
removeArrangedSubview($0)
$0.removeFromSuperview()
}
}
}
...@@ -50,6 +50,54 @@ public enum WeatherType: String, CaseIterable { ...@@ -50,6 +50,54 @@ public enum WeatherType: String, CaseIterable {
} }
} }
public enum WeatherConditionType: CaseIterable {
case precipitation
case humidity
case uvIndex
case pressure
case dewPoint
case visibility
case wind
var localized:String {
switch self {
case .precipitation:
return "condition.precipitation".localized()
case .humidity:
return "condition.humidity".localized()
case .uvIndex:
return "condition.uvIndex".localized()
case .pressure:
return "condition.pressure".localized()
case .dewPoint:
return "condition.dewPoint".localized()
case .visibility:
return "condition.visibility".localized()
case .wind:
return "condition.wind".localized()
}
}
var image:UIImage? {
switch self {
case .precipitation:
return UIImage(named: "precipitation")
case .humidity:
return UIImage(named: "humidity")
case .uvIndex:
return UIImage(named: "uv_index")
case .pressure:
return UIImage(named: "pressure")
case .dewPoint:
return UIImage(named: "dewPoint")
case .visibility:
return UIImage(named: "visibility")
case .wind:
return UIImage(named: "blowingDust")
}
}
}
public enum WindDirection: String, Codable, CaseIterable { public enum WindDirection: String, Codable, CaseIterable {
case north = "N" case north = "N"
case northNorthEast = "NNE" case northNorthEast = "NNE"
......
...@@ -8,18 +8,13 @@ ...@@ -8,18 +8,13 @@
import Foundation import Foundation
public struct DailyWeather: Equatable, Hashable { public struct DailyWeather: Equatable, Hashable {
private static var timeZoneCaledar: Calendar = {
let cal = Calendar(identifier: Calendar.current.identifier)
return cal
}()
public var lastTimeUpdated: Date public var lastTimeUpdated: Date
public var date: Date public var date: Date
public var timeZone: TimeZone public var timeZone: TimeZone
public var weekDay: WeekDay public var weekDay: WeekDay
public var type: WeatherType = .unknown public var type: WeatherType = .unknown
public var isToday: Bool { public var isToday: Bool {
DailyWeather.timeZoneCaledar.timeZone = timeZone return Calendar.timeZoneCalendar(timeZone: timeZone).isDateInToday(date)
return DailyWeather.timeZoneCaledar.isDateInToday(date)
} }
public var minTemp: Temperature? public var minTemp: Temperature?
......
...@@ -46,8 +46,7 @@ public struct Location: Equatable, Hashable { ...@@ -46,8 +46,7 @@ public struct Location: Equatable, Hashable {
public var daily = [DailyWeather]() public var daily = [DailyWeather]()
public var hourly = [HourlyWeather]() { public var hourly = [HourlyWeather]() {
didSet { didSet {
var calendar = Calendar(identifier: .gregorian) let calendar = Calendar.timeZoneCalendar(timeZone: self.timeZone)
calendar.timeZone = self.timeZone
dayTimeForecast = hourly.compactMap { dayTimeForecast = hourly.compactMap {
guard let dayTime = DayTime(hour: calendar.component(.hour, from: $0.date)) else { guard let dayTime = DayTime(hour: calendar.component(.hour, from: $0.date)) else {
......
...@@ -83,7 +83,7 @@ class ForecastDetailPeriodButton: UIControl, PeriodButtonProtocol { ...@@ -83,7 +83,7 @@ class ForecastDetailPeriodButton: UIControl, PeriodButtonProtocol {
} }
func configure(dailyWeather: DailyWeather) { func configure(dailyWeather: DailyWeather) {
if Calendar.current.isDateInToday(dailyWeather.date) { if Calendar.timeZoneCalendar(timeZone: dailyWeather.timeZone).isDateInToday(dailyWeather.date) {
dateLabel.text = "day.today".localized() dateLabel.text = "day.today".localized()
} }
else { else {
...@@ -200,6 +200,7 @@ private extension ForecastDetailPeriodButton { ...@@ -200,6 +200,7 @@ private extension ForecastDetailPeriodButton {
precipLabel.snp.makeConstraints { (make) in precipLabel.snp.makeConstraints { (make) in
make.left.equalTo(precipImageView.snp.right).inset(2) make.left.equalTo(precipImageView.snp.right).inset(2)
make.right.equalToSuperview() make.right.equalToSuperview()
make.centerY.equalTo(precipImageView)
} }
addSubview(container) addSubview(container)
......
...@@ -89,7 +89,7 @@ class ForecastPeriodButton: UIControl, PeriodButtonProtocol { ...@@ -89,7 +89,7 @@ class ForecastPeriodButton: UIControl, PeriodButtonProtocol {
self.minTempLabel.text = dailyWeather.minTemp?.shortString self.minTempLabel.text = dailyWeather.minTemp?.shortString
self.indicatorImageView.image = nil self.indicatorImageView.image = nil
self.forecastImageView.image = dailyWeather.type.image(isDay: true) self.forecastImageView.image = dailyWeather.type.image(isDay: true)
if Calendar.current.isDateInToday(dailyWeather.date) { if Calendar.timeZoneCalendar(timeZone: dailyWeather.timeZone).isDateInToday(dailyWeather.date) {
self.timeLabel.text = "day.today".localized() self.timeLabel.text = "day.today".localized()
} }
else { else {
......
...@@ -16,6 +16,10 @@ private struct HourlyGraphPoints { ...@@ -16,6 +16,10 @@ private struct HourlyGraphPoints {
let points: [CGPoint] let points: [CGPoint]
} }
protocol ForecastTimePeriodViewDelegate:class {
func forecastTimePeriodView(view:ForecastTimePeriodView, didSelectButtonAt index:Int)
}
class ForecastTimePeriodView: UIView { class ForecastTimePeriodView: UIView {
//Private //Private
private let scrollView = UIScrollView() private let scrollView = UIScrollView()
...@@ -28,6 +32,9 @@ class ForecastTimePeriodView: UIView { ...@@ -28,6 +32,9 @@ class ForecastTimePeriodView: UIView {
private var daily = [DailyWeather]() private var daily = [DailyWeather]()
private var hourly = [HourlyWeather]() private var hourly = [HourlyWeather]()
//Public
weak var delegate:ForecastTimePeriodViewDelegate?
//MARK:- View life cycle //MARK:- View life cycle
init() { init() {
super.init(frame: .zero) super.init(frame: .zero)
...@@ -53,17 +60,27 @@ class ForecastTimePeriodView: UIView { ...@@ -53,17 +60,27 @@ class ForecastTimePeriodView: UIView {
} }
} }
public func set(timePeriod:TimePeriod, buttonType: PeriodButtonProtocol.Type) { public func set(timePeriod:TimePeriod, buttonType: PeriodButtonProtocol.Type, selectedIndex:Int = 0) {
self.currentTimePeriod = timePeriod self.currentTimePeriod = timePeriod
rebuildButtons(buttonType: buttonType) rebuildButtons(buttonType: buttonType, selectedIndex: selectedIndex)
} }
//Private //Private
private func rebuildButtons(buttonType: PeriodButtonProtocol.Type) { @objc private func handleForecastButton(button:UIControl) {
stackView.arrangedSubviews.forEach { stackView.arrangedSubviews.enumerated().forEach {
stackView.removeArrangedSubview($0) if let periodButton = $1 as? PeriodButtonProtocol {
$0.removeFromSuperview() periodButton.isSelected = periodButton === button
if periodButton.isSelected {
tintGraphAt(button: periodButton)
delegate?.forecastTimePeriodView(view: self, didSelectButtonAt: $0)
}
}
} }
}
private func rebuildButtons(buttonType: PeriodButtonProtocol.Type, selectedIndex:Int) {
stackView.removeAll()
let numberOfButtons:Int let numberOfButtons:Int
switch currentTimePeriod { switch currentTimePeriod {
...@@ -83,7 +100,7 @@ class ForecastTimePeriodView: UIView { ...@@ -83,7 +100,7 @@ class ForecastTimePeriodView: UIView {
} }
forecastButton.index = index forecastButton.index = index
forecastButton.addTarget(self, action: #selector(handleForecastButton(button:)), for: .touchUpInside) forecastButton.addTarget(self, action: #selector(handleForecastButton(button:)), for: .touchUpInside)
forecastButton.isSelected = index == 1 forecastButton.isSelected = index == selectedIndex
stackView.addArrangedSubview(forecastButton) stackView.addArrangedSubview(forecastButton)
forecastButton.snp.makeConstraints { (make) in forecastButton.snp.makeConstraints { (make) in
...@@ -218,18 +235,6 @@ class ForecastTimePeriodView: UIView { ...@@ -218,18 +235,6 @@ class ForecastTimePeriodView: UIView {
self.graphView.tintMainDotAt(point: hourlyGraphPoints.points[button.index]) self.graphView.tintMainDotAt(point: hourlyGraphPoints.points[button.index])
} }
} }
@objc private func handleForecastButton(button:UIControl) {
stackView.arrangedSubviews.forEach {
if let periodButton = $0 as? PeriodButtonProtocol {
periodButton.isSelected = periodButton === button
if periodButton.isSelected {
tintGraphAt(button: periodButton)
}
}
}
}
} }
private extension ForecastTimePeriodView { private extension ForecastTimePeriodView {
......
...@@ -14,22 +14,32 @@ private enum ForecastCellType:Int, CaseIterable { ...@@ -14,22 +14,32 @@ private enum ForecastCellType:Int, CaseIterable {
// case conditions // case conditions
// case precipitation // case precipitation
// case dayTime // case dayTime
// case sun case sun
// case moon case moon
} }
class ForecastCellFactory { class ForecastCellFactory {
//Private
private let forecastViewModel:ForecastViewModel
//Public
public var forecastPeriodCellFrame:CGRect = .zero public var forecastPeriodCellFrame:CGRect = .zero
public var numberOfRows:Int { public var numberOfRows:Int {
return ForecastCellType.allCases.count return ForecastCellType.allCases.count
} }
public init(viewModel: ForecastViewModel) {
self.forecastViewModel = viewModel
}
public func registerCells(on tableView:UITableView) { public func registerCells(on tableView:UITableView) {
registerCell(type: ForecastTimePeriodCell.self, tableView: tableView) registerCell(type: ForecastTimePeriodCell.self, tableView: tableView)
registerCell(type: ForecastInfoCell.self, tableView: tableView) registerCell(type: ForecastInfoCell.self, tableView: tableView)
registerCell(type: CitySunCell.self, tableView: tableView)
registerCell(type: CityMoonCell.self, tableView: tableView)
} }
public func cellFromTableView(tableView:UITableView, indexPath:IndexPath, viewModel:ForecastViewModel) -> UITableViewCell { public func cellFromTableView(tableView:UITableView, indexPath:IndexPath) -> UITableViewCell {
guard let cellType = ForecastCellType(rawValue: indexPath.row) else { guard let cellType = ForecastCellType(rawValue: indexPath.row) else {
return UITableViewCell() return UITableViewCell()
} }
...@@ -37,20 +47,44 @@ class ForecastCellFactory { ...@@ -37,20 +47,44 @@ class ForecastCellFactory {
switch cellType { switch cellType {
case .forecastPeriod: case .forecastPeriod:
let cell = dequeueReusableCell(type: ForecastTimePeriodCell.self, tableView: tableView, indexPath: indexPath) let cell = dequeueReusableCell(type: ForecastTimePeriodCell.self, tableView: tableView, indexPath: indexPath)
cell.delegate = self
forecastPeriodCellFrame = cell.frame forecastPeriodCellFrame = cell.frame
if let daily = viewModel.location?.daily, if let daily = forecastViewModel.location?.daily,
let hourly = viewModel.location?.hourly { let hourly = forecastViewModel.location?.hourly {
cell.configure(daily: daily, hourly: hourly) cell.configure(daily: daily, hourly: hourly)
} }
return cell return cell
case .forecastInfo: case .forecastInfo:
let cell = dequeueReusableCell(type: ForecastInfoCell.self, tableView: tableView, indexPath: indexPath) let cell = dequeueReusableCell(type: ForecastInfoCell.self, tableView: tableView, indexPath: indexPath)
if let location = viewModel.location, if let daily = forecastViewModel.selectedDailyWeather {
let today = (location.daily.first{$0.isToday}) { cell.configure(dailyWeather: daily)
cell.configure(dailyWeather: today) }
return cell
case .sun:
let cell = dequeueReusableCell(type: CitySunCell.self, tableView: tableView, indexPath: indexPath)
if let loc = forecastViewModel.location {
cell.configure(with: loc)
} }
return cell return cell
case .moon:
let cell = dequeueReusableCell(type: CityMoonCell.self, tableView: tableView, indexPath: indexPath)
if let loc = forecastViewModel.location {
cell.configure(with: loc)
}
return cell
}
}
public func willDisplay(cell:UITableViewCell) {
switch cell {
case let sunCell as CitySunCell:
sunCell.updateSunPosition()
case let moonCell as CityMoonCell:
moonCell.updateMoonPosition()
default:
break
} }
} }
...@@ -64,3 +98,10 @@ class ForecastCellFactory { ...@@ -64,3 +98,10 @@ class ForecastCellFactory {
return cell return cell
} }
} }
//MARK:- ForecastTimePeriodCell Delegate
extension ForecastCellFactory: ForecastTimePeriodCellDelegate {
func timePeriodCell(cell: ForecastTimePeriodCell, didSelectButtonAt index: Int) {
forecastViewModel.selectDailyWeather(at: index)
}
}
//
// ForecastConditionView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
class ForecastConditionView: UIView {
//Private
private let type:WeatherConditionType
private let imageView = UIImageView()
private let valueLabel = UILabel()
private let nameLabel = UILabel()
init(type:WeatherConditionType) {
self.type = type
super.init(frame: .zero)
prepareImageView()
prepareLabels()
}
func configure(dailyWeather:DailyWeather) {
switch type {
case .precipitation:
valueLabel.text = "\(dailyWeather.precipitationProbability ?? 0)%"
case .humidity:
valueLabel.text = "--"
case .uvIndex:
valueLabel.text = "--"
case .pressure:
valueLabel.text = "--"
case .dewPoint:
valueLabel.text = "--"
case .visibility:
valueLabel.text = "--"
case .wind:
let speed = dailyWeather.windSpeed?.string ?? "--"
let direction = dailyWeather.windDirection?.rawValue ?? ""
valueLabel.text = "\(direction) \(speed)"
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension ForecastConditionView {
func prepareImageView() {
imageView.contentMode = .scaleAspectFit
imageView.image = type.image
addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.width.height.equalTo(32)
make.top.left.bottom.equalToSuperview()
}
}
func prepareLabels() {
valueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
valueLabel.font = AppFont.SFPro.bold(size: 14)
valueLabel.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
addSubview(valueLabel)
nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
nameLabel.font = AppFont.SFPro.regular(size: 12)
nameLabel.text = type.localized
nameLabel.setContentCompressionResistancePriority(.init(751), for: .vertical)
addSubview(nameLabel)
//Constraints
valueLabel.snp.makeConstraints { (make) in
make.left.equalTo(imageView.snp.right).offset(10)
make.top.equalToSuperview()
make.right.equalToSuperview()
}
nameLabel.snp.makeConstraints { (make) in
make.left.equalTo(valueLabel)
make.top.equalTo(valueLabel.snp.bottom).offset(5)
make.bottom.equalToSuperview()
make.right.equalToSuperview()
}
}
}
...@@ -35,7 +35,31 @@ class ForecastInfoCell: UITableViewCell { ...@@ -35,7 +35,31 @@ class ForecastInfoCell: UITableViewCell {
let minTemp = dailyWeather.minTemp?.shortString ?? "--" let minTemp = dailyWeather.minTemp?.shortString ?? "--"
tempLabel.text = "\(maxTemp)/\(minTemp)" tempLabel.text = "\(maxTemp)/\(minTemp)"
weatherTypeLabel.text = dailyWeather.type.localized(isDay: true) weatherTypeLabel.text = dailyWeather.type.localized(isDay: true)
weatherDescriptionLabel.text = "Feels like -- - Due to high humidity" weatherDescriptionLabel.text = "Feels like --"
//Conditions
conditionsStackView.removeAll()
let availableConditions:[WeatherConditionType] = [.precipitation, .uvIndex, .wind]
let rowsCount = (CGFloat(WeatherConditionType.allCases.count) / 3).rounded(.up)
var currentRow = -1
var currentConditionsRowStackView = UIStackView()
for (index, type) in availableConditions.enumerated() {
if currentRow != index / Int(rowsCount) {
currentConditionsRowStackView = UIStackView()
currentConditionsRowStackView.axis = .horizontal
currentConditionsRowStackView.distribution = .equalSpacing
conditionsStackView.addArrangedSubview(currentConditionsRowStackView)
}
let conditionView = ForecastConditionView(type: type)
conditionView.configure(dailyWeather: dailyWeather)
currentConditionsRowStackView.addArrangedSubview(conditionView)
currentRow = index / Int(rowsCount)
}
conditionsStackView.layoutIfNeeded()
} }
} }
...@@ -100,11 +124,15 @@ private extension ForecastInfoCell { ...@@ -100,11 +124,15 @@ private extension ForecastInfoCell {
} }
func prepareConditionsStackView() { func prepareConditionsStackView() {
conditionsStackView.axis = .vertical
conditionsStackView.distribution = .fillEqually
conditionsStackView.spacing = 54
conditionsStackView.setContentCompressionResistancePriority(.init(rawValue: 751), for: .vertical)
contentView.addSubview(conditionsStackView) contentView.addSubview(conditionsStackView)
conditionsStackView.snp.makeConstraints { (make) in conditionsStackView.snp.makeConstraints { (make) in
make.top.equalTo(topContainer.snp.bottom).offset(15) make.top.equalTo(topContainer.snp.bottom).offset(15)
make.bottom.equalToSuperview().inset(15) make.bottom.equalToSuperview().inset(15).priority(.init(999))
make.left.right.equalToSuperview().inset(18) make.left.right.equalToSuperview().inset(18)
} }
} }
......
//
// DayTimePrecipitationView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
class DayTimePrecipitationView: UIView {
//Private
private let dayTimeLabel = UILabel()
private let precipitationView = PrecipitationView()
private let valueLabel = UILabel()
init(withSeparator:Bool = false) {
super.init(frame: .zero)
prepareView()
prepareLabels()
preparePrecipView()
if withSeparator {
prepareSeparator()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension DayTimePrecipitationView {
func prepareView() {
self.snp.makeConstraints { (make) in
make.width.equalTo(90)
}
}
func prepareLabels() {
dayTimeLabel.font = AppFont.SFPro.bold(size: 14)
dayTimeLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
addSubview(dayTimeLabel)
valueLabel.font = AppFont.SFPro.bold(size: 12)
valueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
addSubview(valueLabel)
//Constraints
dayTimeLabel.snp.makeConstraints { (make) in
make.top.equalToSuperview().inset(24)
make.centerX.equalToSuperview()
}
valueLabel.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().inset(18)
}
}
func preparePrecipView() {
addSubview(precipitationView)
precipitationView.snp.makeConstraints { (make) in
make.top.equalTo(dayTimeLabel.snp.bottom).offset(24).priority(.medium)
make.bottom.equalTo(valueLabel.snp.top).offset(-20)
make.width.equalTo(24)
make.centerX.equalToSuperview()
make.height.equalTo(130)
}
}
func prepareSeparator() {
let separatorView = UIView()
separatorView.backgroundColor = UIColor(hex: 0xc2c9d6)
separatorView.alpha = 0.5
addSubview(separatorView)
separatorView.snp.makeConstraints { (make) in
make.top.equalTo(dayTimeLabel)
make.bottom.equalTo(precipitationView)
make.right.equalToSuperview()
make.width.equalTo(1)
}
}
}
//
// ForecastPrecipitationCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
class ForecastPrecipitationCell: UITableViewCell {
//Private
private let titleLabel = UILabel()
private let stackView = UIStackView()
private let infoContainer = UIView()
private let infoImageView = UIImageView()
private let infoLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareTitle()
prepareStackView()
prepareInfoContainer()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK:- Prepare
private extension ForecastPrecipitationCell {
func prepareCell() {
selectionStyle = .none
contentView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
func prepareTitle() {
titleLabel.font = AppFont.SFPro.bold(size: 16)
titleLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
titleLabel.text = "precipitation.title".localized().uppercased()
contentView.addSubview(titleLabel)
}
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.alignment = .center
stackView.spacing = 0
stackView.clipsToBounds = false
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 6, bottom: 0, right: 6)
contentView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.top.equalTo(titleLabel.snp.bottom).offset(18)
make.left.right.equalToSuperview().inset(18)
}
}
func prepareInfoContainer() {
infoImageView.contentMode = .scaleAspectFit
infoImageView.image = UIImage(named: "humidity")
infoContainer.addSubview(infoImageView)
infoImageView.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(20)
make.centerY.equalToSuperview()
make.width.height.equalTo(12)
}
infoLabel.font = AppFont.SFPro.regular(size: 13)
infoLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
infoLabel.text = "Possible light shower between 1 PM - 2 PM"
infoContainer.addSubview(infoLabel)
infoLabel.snp.makeConstraints { (make) in
make.left.equalTo(infoImageView.snp.right).offset(8)
make.right.equalToSuperview().inset(8)
make.centerY.equalToSuperview()
}
infoContainer.backgroundColor = UIColor(hex: 0xd9ebfe)
infoContainer.layer.cornerRadius = 12
contentView.addSubview(infoContainer)
infoContainer.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.height.equalTo(40)
make.top.equalTo(stackView.snp.bottom).offset(20).priority(.medium)
make.bottom.equalToSuperview().inset(15)
}
}
}
//
// DayTimePrecipitationView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.03.2021.
//
import UIKit
class DayTimePrecipitationView: UIView {
//Private
private let dai
init(withSeparator:Bool = false) {
super.init(frame: .zero)
}
}
private extension DayTimePrecipitationView {
func prepareLabels() {
}
func preparePrecipView() {
}
func prepareSeparator() {
}
}
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
import UIKit import UIKit
protocol ForecastTimePeriodCellDelegate:class {
func timePeriodCell(cell:ForecastTimePeriodCell, didSelectButtonAt index:Int)
}
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(),
...@@ -17,6 +21,9 @@ class ForecastTimePeriodCell: UITableViewCell { ...@@ -17,6 +21,9 @@ class ForecastTimePeriodCell: UITableViewCell {
endColor: UIColor(hex: 0xdaddec), endColor: UIColor(hex: 0xdaddec),
opacity: 0.5) opacity: 0.5)
private var graphIsDrawn = false private var graphIsDrawn = false
//Public
weak var delegate:ForecastTimePeriodCellDelegate?
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)
...@@ -74,6 +81,7 @@ private extension ForecastTimePeriodCell { ...@@ -74,6 +81,7 @@ private extension ForecastTimePeriodCell {
} }
func prepareForecastTimePeriodView() { func prepareForecastTimePeriodView() {
forecastTimePeriodView.delegate = self
contentView.addSubview(forecastTimePeriodView) contentView.addSubview(forecastTimePeriodView)
forecastTimePeriodView.snp.makeConstraints { (make) in forecastTimePeriodView.snp.makeConstraints { (make) in
make.left.equalToSuperview() make.left.equalToSuperview()
...@@ -92,3 +100,10 @@ private extension ForecastTimePeriodCell { ...@@ -92,3 +100,10 @@ private extension ForecastTimePeriodCell {
} }
} }
} }
//MARK:- ForecastTimePeriodView Delegate
extension ForecastTimePeriodCell: ForecastTimePeriodViewDelegate {
func forecastTimePeriodView(view: ForecastTimePeriodView, didSelectButtonAt index: Int) {
self.delegate?.timePeriodCell(cell: self, didSelectButtonAt: index)
}
}
...@@ -112,9 +112,6 @@ private class DayControlButton: UIControl { ...@@ -112,9 +112,6 @@ private class DayControlButton: UIControl {
fmt.dateFormat = "d, E" fmt.dateFormat = "d, E"
return fmt return fmt
}() }()
private static var calendar:Calendar = {
return Calendar(identifier: Calendar.current.identifier)
}()
//Public //Public
var index:Int = -1 var index:Int = -1
...@@ -166,9 +163,8 @@ private class DayControlButton: UIControl { ...@@ -166,9 +163,8 @@ private class DayControlButton: UIControl {
func configure(dailyWeather: DailyWeather) { func configure(dailyWeather: DailyWeather) {
DayControlButton.dateFormatter.timeZone = dailyWeather.timeZone DayControlButton.dateFormatter.timeZone = dailyWeather.timeZone
DayControlButton.calendar.timeZone = dailyWeather.timeZone
if DayControlButton.calendar.isDateInToday(dailyWeather.date) { if Calendar.timeZoneCalendar(timeZone: dailyWeather.timeZone).isDateInToday(dailyWeather.date) {
dayLabel.text = "day.today".localized() dayLabel.text = "day.today".localized()
} }
else { else {
......
...@@ -9,6 +9,7 @@ import UIKit ...@@ -9,6 +9,7 @@ import UIKit
class ForecastViewController: UIViewController { class ForecastViewController: UIViewController {
//Private //Private
private let forecastCellFactory:ForecastCellFactory
private let cityButton = NavigationCityButton() private let cityButton = NavigationCityButton()
private let daysControlView = DaysControlView() private let daysControlView = DaysControlView()
private let tableView = UITableView() private let tableView = UITableView()
...@@ -17,6 +18,7 @@ class ForecastViewController: UIViewController { ...@@ -17,6 +18,7 @@ class ForecastViewController: UIViewController {
init(viewModel: ForecastViewModel) { init(viewModel: ForecastViewModel) {
self.viewModel = viewModel self.viewModel = viewModel
self.forecastCellFactory = ForecastCellFactory(viewModel: viewModel)
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
} }
...@@ -91,7 +93,7 @@ private extension ForecastViewController { ...@@ -91,7 +93,7 @@ private extension ForecastViewController {
} }
func prepareTableView() { func prepareTableView() {
viewModel.forecastCellFactory.registerCells(on: tableView) forecastCellFactory.registerCells(on: tableView)
tableView.contentInset = .init(top: 0, left: 0, bottom: 15, right: 0) tableView.contentInset = .init(top: 0, left: 0, bottom: 15, right: 0)
tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
tableView.separatorStyle = .none tableView.separatorStyle = .none
...@@ -111,18 +113,22 @@ private extension ForecastViewController { ...@@ -111,18 +113,22 @@ private extension ForecastViewController {
//MARK:- UITableView Delegate //MARK:- UITableView Delegate
extension ForecastViewController: UITableViewDelegate { extension ForecastViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
let cellFrame = viewModel.forecastCellFactory.forecastPeriodCellFrame let cellFrame = forecastCellFactory.forecastPeriodCellFrame
guard guard
let navVC = self.navigationController, let navVC = self.navigationController,
cellFrame.origin.y > 0,
viewModel.location?.daily.isEmpty == false viewModel.location?.daily.isEmpty == false
else { else {
return return
} }
let startPointY = cellFrame.origin.y + cellFrame.height - self.daysControlView.frame.height let startPointY = cellFrame.origin.y + cellFrame.height - self.daysControlView.frame.height
guard startPointY > 0 else {
//Force reload row to get actual row height
tableView.reloadRows(at: [[0,0]], with: .none)
return
}
if scrollView.contentOffset.y >= startPointY { if scrollView.contentOffset.y >= startPointY {
if !navVC.isNavigationBarHidden { if !navVC.isNavigationBarHidden {
navVC.setNavigationBarHidden(true, animated: true) navVC.setNavigationBarHidden(true, animated: true)
...@@ -140,23 +146,26 @@ extension ForecastViewController: UITableViewDelegate { ...@@ -140,23 +146,26 @@ extension ForecastViewController: UITableViewDelegate {
} }
} }
} }
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
forecastCellFactory.willDisplay(cell: cell)
}
} }
//MARK:- UITableView DataSource //MARK:- UITableView DataSource
extension ForecastViewController: UITableViewDataSource { extension ForecastViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.forecastCellFactory.numberOfRows return forecastCellFactory.numberOfRows
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return viewModel.forecastCellFactory.cellFromTableView(tableView: tableView, return forecastCellFactory.cellFromTableView(tableView: tableView,
indexPath: indexPath, indexPath: indexPath)
viewModel: viewModel)
} }
} }
//MARK:- ViewModel Delegate //MARK:- ViewModel Delegate
extension ForecastViewController: ViewModelDelegate { extension ForecastViewController: ForecastViewModelDelegate {
func viewModelDidChange<P>(model: P) where P : ViewModelProtocol { func viewModelDidChange<P>(model: P) where P : ViewModelProtocol {
refreshCityButton() refreshCityButton()
tableView.reloadData() tableView.reloadData()
...@@ -168,4 +177,15 @@ extension ForecastViewController: ViewModelDelegate { ...@@ -168,4 +177,15 @@ extension ForecastViewController: ViewModelDelegate {
daysControlView.alpha = 0 daysControlView.alpha = 0
} }
} }
func selectedDailyWeatherDidChange() {
var indexPathToReload = [IndexPath]()
for index in 0..<forecastCellFactory.numberOfRows {
if index == 0 { continue }
indexPathToReload.append([0, index])
}
tableView.reloadRows(at: indexPathToReload, with: .none)
}
} }
...@@ -7,35 +7,25 @@ ...@@ -7,35 +7,25 @@
import UIKit import UIKit
enum CityConditionButtonType: CaseIterable {
case precipitation
case humidity
case uvIndex
case pressure
case dewPoint
case visibility
case wind
}
class CityConditionButton: UIControl { class CityConditionButton: UIControl {
//Private //Private
private let buttonType:CityConditionButtonType private let conditionType:WeatherConditionType
private let imageView = UIImageView() private let imageView = UIImageView()
private let valueLabel = UILabel() private let valueLabel = UILabel()
private let nameLabel = UILabel() private let nameLabel = UILabel()
private let descriptionLabel = UILabel() private let descriptionLabel = UILabel()
init(type: CityConditionButtonType) { init(type: WeatherConditionType) {
self.buttonType = type self.conditionType = type
super.init(frame: .zero) super.init(frame: .zero)
prepareButton() prepareButton()
prepareImageView(type: type) prepareImageView()
prepareLabels(type: type) prepareLabels()
} }
public func configure(with location:Location) { public func configure(with location:Location) {
switch buttonType { switch conditionType {
case .precipitation: case .precipitation:
valueLabel.text = "\(location.today?.precipitationProbability ?? 0)%" valueLabel.text = "\(location.today?.precipitationProbability ?? 0)%"
case .humidity: case .humidity:
...@@ -55,44 +45,6 @@ class CityConditionButton: UIControl { ...@@ -55,44 +45,6 @@ class CityConditionButton: UIControl {
} }
} }
private func image(for type:CityConditionButtonType) -> UIImage? {
switch type {
case .precipitation:
return UIImage(named: "precipitation")
case .humidity:
return UIImage(named: "humidity")
case .uvIndex:
return UIImage(named: "uv_index")
case .pressure:
return UIImage(named: "pressure")
case .dewPoint:
return UIImage(named: "dewPoint")
case .visibility:
return UIImage(named: "visibility")
case .wind:
return UIImage(named: "blowingDust")
}
}
private func title(for type:CityConditionButtonType) -> String {
switch type {
case .precipitation:
return "condition.precipitation".localized()
case .humidity:
return "condition.humidity".localized()
case .uvIndex:
return "condition.uvIndex".localized()
case .pressure:
return "condition.pressure".localized()
case .dewPoint:
return "condition.dewPoint".localized()
case .visibility:
return "condition.visibility".localized()
case .wind:
return "condition.wind".localized()
}
}
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
...@@ -110,11 +62,11 @@ private extension CityConditionButton { ...@@ -110,11 +62,11 @@ private extension CityConditionButton {
self.layer.shadowRadius = 20 self.layer.shadowRadius = 20
} }
func prepareImageView(type: CityConditionButtonType) { func prepareImageView() {
imageView.isUserInteractionEnabled = false imageView.isUserInteractionEnabled = false
imageView.contentMode = .scaleAspectFit imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true imageView.clipsToBounds = true
imageView.image = image(for: type) imageView.image = conditionType.image
addSubview(imageView) addSubview(imageView)
imageView.snp.makeConstraints { (make) in imageView.snp.makeConstraints { (make) in
...@@ -123,7 +75,7 @@ private extension CityConditionButton { ...@@ -123,7 +75,7 @@ private extension CityConditionButton {
} }
} }
func prepareLabels(type: CityConditionButtonType) { func prepareLabels() {
//Value //Value
valueLabel.isUserInteractionEnabled = false valueLabel.isUserInteractionEnabled = false
valueLabel.font = AppFont.SFPro.bold(size: 18) valueLabel.font = AppFont.SFPro.bold(size: 18)
...@@ -142,7 +94,7 @@ private extension CityConditionButton { ...@@ -142,7 +94,7 @@ private extension CityConditionButton {
nameLabel.font = AppFont.SFPro.regular(size: 16) nameLabel.font = AppFont.SFPro.regular(size: 16)
nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
nameLabel.textAlignment = .left nameLabel.textAlignment = .left
nameLabel.text = title(for: type) nameLabel.text = conditionType.localized
addSubview(nameLabel) addSubview(nameLabel)
nameLabel.snp.makeConstraints { (make) in nameLabel.snp.makeConstraints { (make) in
......
...@@ -75,7 +75,7 @@ private extension CityConditionsCell { ...@@ -75,7 +75,7 @@ private extension CityConditionsCell {
make.edges.height.equalToSuperview() make.edges.height.equalToSuperview()
} }
for conditionType in CityConditionButtonType.allCases { for conditionType in WeatherConditionType.allCases {
let button = CityConditionButton(type: conditionType) let button = CityConditionButton(type: conditionType)
stackView.addArrangedSubview(button) stackView.addArrangedSubview(button)
} }
......
...@@ -16,7 +16,7 @@ class PrecipButton: UIControl { ...@@ -16,7 +16,7 @@ class PrecipButton: UIControl {
return fmt return fmt
}() }()
private let valueLabel = UILabel() private let valueLabel = UILabel()
private let precipView = PrecipView() private let precipView = PrecipitationView()
private let timeLabel = UILabel() private let timeLabel = UILabel()
override init(frame: CGRect) { override init(frame: CGRect) {
...@@ -65,7 +65,7 @@ class PrecipButton: UIControl { ...@@ -65,7 +65,7 @@ class PrecipButton: UIControl {
self.precipView.set(value: CGFloat(percent)/100.0) self.precipView.set(value: CGFloat(percent)/100.0)
self.valueLabel.text = "\(Int(percent))%" self.valueLabel.text = "\(Int(percent))%"
if Calendar.current.isDateInToday(daily.date) { if Calendar.timeZoneCalendar(timeZone: daily.timeZone).isDateInToday(daily.date) {
self.timeLabel.text = "day.today".localized() self.timeLabel.text = "day.today".localized()
} }
else { else {
......
// //
// PrecipView.swift // PrecipitationView.swift
// 1Weather // 1Weather
// //
// Created by Dmitry Stepanets on 04.03.2021. // Created by Dmitry Stepanets on 04.03.2021.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import UIKit import UIKit
class PrecipView: UIView { class PrecipitationView: UIView {
//Private //Private
private let kLevelWidth:CGFloat = 12 private let kLevelWidth:CGFloat = 12
private let kCapHeight:CGFloat = 4 private let kCapHeight:CGFloat = 4
...@@ -60,7 +60,7 @@ class PrecipView: UIView { ...@@ -60,7 +60,7 @@ class PrecipView: UIView {
} }
//MARK:- Prepare //MARK:- Prepare
private extension PrecipView { private extension PrecipitationView {
func prepareView() { func prepareView() {
//Main //Main
levelLayer.masksToBounds = true levelLayer.masksToBounds = true
......
...@@ -7,11 +7,15 @@ ...@@ -7,11 +7,15 @@
import UIKit import UIKit
protocol ForecastViewModelDelegate:ViewModelDelegate {
func selectedDailyWeatherDidChange()
}
class ForecastViewModel: ViewModelProtocol { class ForecastViewModel: ViewModelProtocol {
//Public //Public
public let forecastCellFactory = ForecastCellFactory() public weak var delegate:ForecastViewModelDelegate?
public weak var delegate:ViewModelDelegate?
public private(set) var location: Location? public private(set) var location: Location?
public private(set) var selectedDailyWeather:DailyWeather?
//Private //Private
private var locationManager: LocationManager private var locationManager: LocationManager
...@@ -28,6 +32,21 @@ class ForecastViewModel: ViewModelProtocol { ...@@ -28,6 +32,21 @@ class ForecastViewModel: ViewModelProtocol {
public func updateWeather() { public func updateWeather() {
locationManager.updateWeather() locationManager.updateWeather()
} }
public func select(dailyWeather:DailyWeather) {
self.selectedDailyWeather = dailyWeather
self.delegate?.selectedDailyWeatherDidChange()
}
public func selectDailyWeather(at index:Int) {
guard let daily = location?.daily else { return }
daily.enumerated().forEach {
if $0 == index {
self.selectedDailyWeather = $1
}
}
self.delegate?.selectedDailyWeatherDidChange()
}
} }
//MARK:- LocationManager Delegate //MARK:- LocationManager Delegate
...@@ -36,6 +55,7 @@ extension ForecastViewModel: LocationManagerDelegate { ...@@ -36,6 +55,7 @@ extension ForecastViewModel: LocationManagerDelegate {
DispatchQueue.main.async { DispatchQueue.main.async {
print("TVM-Forecast") print("TVM-Forecast")
self.location = newLocation self.location = newLocation
self.selectDailyWeather(at: 0)
self.delegate?.viewModelDidChange(model: self) self.delegate?.viewModelDidChange(model: self)
} }
} }
......
...@@ -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>5</integer> <integer>6</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