Commit 1394c534 by Dmitriy Stepanets

Finished saved cities UI

parent f008382b
......@@ -19,6 +19,9 @@
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; };
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; };
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B304225726AD1004B34B3 /* DefaultTheme.swift */; };
CDA69B2C2574F3C800CB6409 /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B2B2574F3C800CB6409 /* CityCell.swift */; };
CDA69B30257500E200CB6409 /* GeoNamesPlace.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */; };
CDA69B3325750D3400CB6409 /* LocationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */; };
CDCC480E255EA8D2003EA8A7 /* CurrentForecastDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCC480D255EA8D2003EA8A7 /* CurrentForecastDetailsCell.swift */; };
CDD0F1DF2572403E00CF5017 /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */; };
CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CDD0F1E42572425200CF5017 /* SF-Pro.ttf */; };
......@@ -43,6 +46,9 @@
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>"; };
CD6B304225726AD1004B34B3 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; };
CDA69B2B2574F3C800CB6409 /* CityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoNamesPlace.swift; sourceTree = "<group>"; };
CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsViewModel.swift; sourceTree = "<group>"; };
CDCC480D255EA8D2003EA8A7 /* CurrentForecastDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentForecastDetailsCell.swift; sourceTree = "<group>"; };
CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; };
CDD0F1E42572425200CF5017 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = "<group>"; };
......@@ -96,7 +102,6 @@
CD1237DA255D5DFA00C98139 /* PG.playground */,
CDD0F1DC2572400200CF5017 /* UI */,
CD1237EF255D838600C98139 /* Extensions */,
CD1237E5255D74C400C98139 /* Common */,
CDD0F1E2257240DA00CF5017 /* Resources */,
CD1237C2255D5C5900C98139 /* AppDelegate.swift */,
CD1237C6255D5C5900C98139 /* ViewController.swift */,
......@@ -115,13 +120,6 @@
path = Cells;
sourceTree = "<group>";
};
CD1237E5255D74C400C98139 /* Common */ = {
isa = PBXGroup;
children = (
);
path = Common;
sourceTree = "<group>";
};
CD1237EF255D838600C98139 /* Extensions */ = {
isa = PBXGroup;
children = (
......@@ -156,9 +154,26 @@
path = Themes;
sourceTree = "<group>";
};
CDA69B2A2574F3A700CB6409 /* Cells */ = {
isa = PBXGroup;
children = (
CDA69B2B2574F3C800CB6409 /* CityCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
CDA69B2E2575008700CB6409 /* Models */ = {
isa = PBXGroup;
children = (
CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */,
);
path = Models;
sourceTree = "<group>";
};
CDD0F1DC2572400200CF5017 /* UI */ = {
isa = PBXGroup;
children = (
CDA69B2E2575008700CB6409 /* Models */,
CD6B3039257267FB004B34B3 /* Buttons */,
CD6B3038257267E2004B34B3 /* View controllers */,
CD1237DC255D60EC00C98139 /* Cells */,
......@@ -170,7 +185,9 @@
CDD0F1DD2572401000CF5017 /* Location */ = {
isa = PBXGroup;
children = (
CDA69B2A2574F3A700CB6409 /* Cells */,
CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */,
CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */,
);
path = Location;
sourceTree = "<group>";
......@@ -328,6 +345,7 @@
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */,
CDD0F1DF2572403E00CF5017 /* LocationViewController.swift in Sources */,
CDCC480E255EA8D2003EA8A7 /* CurrentForecastDetailsCell.swift in Sources */,
CDA69B30257500E200CB6409 /* GeoNamesPlace.swift in Sources */,
CD1237C7255D5C5900C98139 /* ViewController.swift in Sources */,
CD1237F1255D83C500C98139 /* UIColor+Hex.swift in Sources */,
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */,
......@@ -335,8 +353,10 @@
CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */,
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */,
CD6B3036257262C2004B34B3 /* UIColor+Highlight.swift in Sources */,
CDA69B3325750D3400CB6409 /* LocationsViewModel.swift in Sources */,
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */,
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */,
CDA69B2C2574F3C800CB6409 /* CityCell.swift in Sources */,
CD1237DE255D612300C98139 /* CurrentForecastCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
......
......@@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
ThemeManager.refreshAppearance()
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = UINavigationController(rootViewController: LocationViewController())
self.window?.rootViewController = LocationViewController()
self.window?.makeKeyAndVisible()
return true
......
{
"images" : [
{
"filename" : "city_checkmark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "edit_pencil.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
......@@ -24,5 +24,13 @@ public struct AppFont {
return font
}
static func semibold(size: CGFloat) -> UIFont {
guard let font = UIFont(name: "SFPro-Semibold", size: size) else {
return UIFont.systemFont(ofSize: size, weight: .semibold)
}
return font
}
}
}
......@@ -10,6 +10,10 @@ import UIKit
public struct ThemeManager {
struct Colors {
static let locationBlue = UIColor(hex: 0x1071F0)
static let temperatureLabelBG = UIColor(hex: 0x5F5F5F)
static let citySelected = UIColor(hex: 0x599A0E)
static let cityNoSelected = UIColor(hex: 0xC5C5C5).withAlphaComponent(0.5)
static let cityAddButtonBG = UIColor(hex: 0x131315)
}
static var currentTheme:ThemeProtocol {
......
//
// GeoNamesPlace.swift
// OneWeather
//
// Created by Steven G Pint on 8/21/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import Foundation
class GeoNamesPlace: NSObject {
var latitude: String?
var longitude : String?
var city : String?
var state : String?
var stateCode : String?
var country : String?
var countryCode : String?
var fcodeName : String? // airport if airport
var toponymName : String? // airport name if fcodeName is airport
var isMyLocation: Bool = false
func detailName() -> String {
var sb = String()
if let state = self.state {
if state.count > 0 {
sb.append(state)
sb.append(", ")
}
}
if let country = self.country {
if country.count > 0 {
sb.append(country)
}
} else if let code = self.countryCode {
if code.count > 0 {
sb.append(code)
}
}
return sb
}
var fullName: String {
get {
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if state?.count ?? 0 > 0 {
sb.append(state!)
sb.append(", ")
}
if country?.count ?? 0 > 0 {
sb.append(country!)
sb.append(", ")
}
if (sb.count > 0) {
// sb = sb.trim()
}
return sb
}
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? GeoNamesPlace {
return fullName == object.fullName
} else {
return false
}
}
override var hash: Int {
return fullName.hashValue
}
}
//
// CityCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
class CityCell: UITableViewCell {
//Public
static let kIdentifier = "cityCell"
//Private
private let cityLabel = UILabel()
private let selectedButton = UIButton()
private let addButton = UIButton()
private let temperatureLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareCityLabel()
prepareAddButton()
prepareSelectButton()
prepareTemperatureLabel()
}
func configure(geoName:GeoNamesPlace, mode:LocationsViewModelDisplayMode) {
cityLabel.text = geoName.city
addButton.isHidden = mode == .savedCities
selectedButton.isHidden = mode != .savedCities
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK: Prepare
private extension CityCell {
func prepareCell() {
contentView.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
selectionStyle = .none
}
func prepareCityLabel() {
cityLabel.font = AppFont.SFPro.regular(size: 14)
cityLabel.numberOfLines = 1
cityLabel.lineBreakMode = .byTruncatingTail
cityLabel.textColor = ThemeManager.currentTheme.primaryTextColor
cityLabel.setContentHuggingPriority(.init(1000), for: .horizontal)
contentView.addSubview(cityLabel)
cityLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareTemperatureLabel() {
let container = UIView()
container.backgroundColor = ThemeManager.Colors.temperatureLabelBG
container.clipsToBounds = true
container.layer.cornerRadius = 5
container.setContentHuggingPriority(.init(1000), for: .horizontal)
temperatureLabel.text = "27°"
temperatureLabel.font = AppFont.SFPro.semibold(size: 16)
temperatureLabel.textColor = ThemeManager.currentTheme.primaryTextColor
temperatureLabel.setContentHuggingPriority(.init(1000), for: .horizontal)
container.addSubview(temperatureLabel)
temperatureLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(6)
make.right.equalToSuperview().inset(6)
make.top.equalToSuperview().inset(4)
make.bottom.equalToSuperview().inset(4)
}
contentView.addSubview(container)
container.snp.makeConstraints { (make) in
make.left.equalTo(cityLabel.snp.right).offset(14)
make.centerY.equalTo(cityLabel)
make.right.lessThanOrEqualTo(addButton.snp.left).offset(-14)
}
}
func prepareAddButton() {
addButton.isHidden = false
addButton.setTitle("+ ADD", for: .normal)
addButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
addButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
addButton.backgroundColor = ThemeManager.Colors.cityAddButtonBG
addButton.layer.cornerRadius = 12
addButton.layer.shadowOffset = .init(width: 0, height: 2)
addButton.layer.shadowColor = UIColor.black.cgColor
addButton.layer.shadowRadius = 2
addButton.layer.shadowOpacity = 0.5
addButton.layer.shadowPath = UIBezierPath(roundedRect: .init(origin: .zero, size: .init(width: 55, height: 24)),
cornerRadius: 12).cgPath
contentView.addSubview(addButton)
addButton.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 55, height: 24))
make.right.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareSelectButton() {
selectedButton.isHidden = true
selectedButton.imageView?.contentMode = .scaleAspectFit
selectedButton.setImage(UIImage(named: "city_checkmark"), for: .normal)
selectedButton.tintColor = ThemeManager.Colors.cityNoSelected
contentView.addSubview(selectedButton)
selectedButton.snp.makeConstraints { (make) in
make.right.equalToSuperview().inset(24)
make.size.equalTo(CGSize(width: 24, height: 24))
make.centerY.equalToSuperview()
}
}
}
......@@ -7,12 +7,39 @@
import UIKit
class LocationViewController: UIViewController {
//MARK:- Location Navigation View Controller
class LocationViewController: UINavigationController {
init() {
let savedCitiesViewController = SavedCitiesViewController()
super.init(rootViewController: savedCitiesViewController)
}
override convenience init(rootViewController: UIViewController) {
self.init()
}
override convenience init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.init()
}
override convenience init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) {
self.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private class SavedCitiesViewController:UIViewController {
//Private
private let searchBar = UISearchBar()
private let locationButton = SelfSizingButton(frame: .zero)
private let locationsViewModel = LocationsViewModel()
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
view.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
edgesForExtendedLayout = []
......@@ -20,19 +47,24 @@ class LocationViewController: UIViewController {
prepareNavBar()
prepareSearchBar()
prepareLocationButton()
prepareTableView()
}
@objc private func handleCloseButton() {
self.navigationController?.dismiss(animated: true)
}
@objc private func handleLocationButton() {
}
@objc private func handleEditButton() {
}
}
//MARK:- Prepare
private extension LocationViewController {
//MARK:- Saved cities prepare
private extension SavedCitiesViewController {
func prepareNavBar() {
self.navigationController?.view.backgroundColor = .white
......@@ -72,7 +104,6 @@ private extension LocationViewController {
}
func prepareLocationButton() {
let locationButton = SelfSizingButton(frame: .zero)
locationButton.tintColor = ThemeManager.Colors.locationBlue
locationButton.setImage(UIImage(named: "location_arrow"), for: .normal)
locationButton.setTitle("Use Current Location", for: .normal)
......@@ -86,7 +117,79 @@ private extension LocationViewController {
view.addSubview(locationButton)
locationButton.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(16)
make.top.equalTo(searchBar.snp.bottom).offset(12)
make.top.equalTo(searchBar.snp.bottom).offset(4)
}
}
func prepareTableView() {
let tableView = UITableView()
tableView.backgroundColor = view.backgroundColor
tableView.register(CityCell.self, forCellReuseIdentifier: CityCell.kIdentifier)
tableView.tableFooterView = UIView()
tableView.rowHeight = 42
tableView.delegate = self
tableView.dataSource = self
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.top.equalTo(locationButton.snp.bottom).offset(24)
make.left.equalToSuperview()
make.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
}
//MARK: UItableView Data Source
extension SavedCitiesViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationsViewModel.savedCities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CityCell.kIdentifier, for: indexPath) as! CityCell
cell.configure(geoName: locationsViewModel.savedCities[indexPath.row],
mode: locationsViewModel.displayMode)
return cell
}
}
//MARK: UITableView Delegate
extension SavedCitiesViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let container = UIView()
container.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
let titleLabel = UILabel()
titleLabel.text = "Saved Cities (\(self.locationsViewModel.savedCities.count))"
titleLabel.font = AppFont.SFPro.bold(size: 24)
titleLabel.textColor = ThemeManager.currentTheme.primaryTextColor
container.addSubview(titleLabel)
if locationsViewModel.displayMode == .savedCities {
let editButton = SelfSizingButton()
editButton.setImage(UIImage(named: "edit_pencil"), for: .normal)
editButton.tintColor = ThemeManager.Colors.locationBlue
editButton.setTitle("EDIT", for: .normal)
editButton.setTitleColor(ThemeManager.Colors.locationBlue.highlighted, for: .highlighted)
editButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
editButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
editButton.titleEdgeInsets = .init(top: 0, left: 6, bottom: 0, right: 0)
editButton.addTarget(self, action: #selector(handleEditButton), for: .touchUpInside)
container.addSubview(editButton)
editButton.snp.makeConstraints { (make) in
make.right.equalToSuperview().inset(24)
make.bottom.equalTo(titleLabel)
}
}
titleLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(24)
make.top.equalToSuperview()
make.bottom.equalToSuperview().inset(24)
}
return container
}
}
//
// LocationsViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
protocol LocationsViewModelDelegate:class {
func viewModelDidChange(model:LocationsViewModel)
func viewModelDisplayModeDidChange(model: LocationsViewModel)
func viewModel(model:LocationsViewModel, errorHasOccured error:Error)
}
enum LocationsViewModelDisplayMode {
case savedCities
case popularCities
case searchResult
}
class LocationsViewModel {
//Public
weak var delegate:LocationsViewModelDelegate?
private(set) var savedCities = [GeoNamesPlace]()
private(set) var displayMode:LocationsViewModelDisplayMode = .savedCities
init() {
testGeo()
}
private func testGeo() {
let perm = GeoNamesPlace()
perm.city = "Perm asfaskdf aslojkraijasijaowierhopaiwr"
perm.stateCode = "614000"
perm.country = "Russia"
let moscow = GeoNamesPlace()
moscow.city = "Moscow"
moscow.stateCode = "000000"
moscow.country = "Russia"
let sp = GeoNamesPlace()
sp.city = "Saint-Petersburg"
sp.stateCode = "000000"
sp.country = "Russia"
let kazan = GeoNamesPlace()
kazan.city = "Kazan"
kazan.stateCode = "000000"
kazan.country = "Russia"
savedCities = [perm, moscow, sp, kazan]
}
func fetchCities(query:String) {
//Some fetching method
//Tell delegate to refresh UI
self.delegate?.viewModelDidChange(model: self)
}
func add(city:GeoNamesPlace) {
//Tell delegate to refresh UI
self.delegate?.viewModelDidChange(model: self)
}
func delete(city:GeoNamesPlace) {
//Tell delegate to refresh UI
self.delegate?.viewModelDidChange(model: self)
}
func select(city:GeoNamesPlace) {
//Tell delegate to refresh UI
self.delegate?.viewModelDidChange(model: self)
}
}
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