diff --git a/ios/MapPing.xcodeproj/project.pbxproj b/ios/MapPing.xcodeproj/project.pbxproj index b848def..6796d30 100644 --- a/ios/MapPing.xcodeproj/project.pbxproj +++ b/ios/MapPing.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 665624242067128700F37197 /* HttpService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665624232067128700F37197 /* HttpService.swift */; }; + 6656242820671A7900F37197 /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6656242720671A7900F37197 /* FailableDecodable.swift */; }; + 6656242A20671BE300F37197 /* PartType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6656242920671BE300F37197 /* PartType.swift */; }; + 6656242D20672F4900F37197 /* DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6656242C20672F4900F37197 /* DetailsView.swift */; }; + 6656242F20672F5600F37197 /* DetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6656242E20672F5600F37197 /* DetailsViewController.swift */; }; + 93695A4C20671488007D1410 /* PartClusterAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93695A4B20671488007D1410 /* PartClusterAnnotationView.swift */; }; 940F0B61F668AD88441334C2 /* Pods_MapPing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16CD00BB887C10CE3E91B52C /* Pods_MapPing.framework */; }; F1196A76206481B60084672D /* PartAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1196A75206481B60084672D /* PartAnnotationView.swift */; }; F1196A782064857D0084672D /* PartAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1196A772064857D0084672D /* PartAnnotation.swift */; }; @@ -38,6 +44,12 @@ /* Begin PBXFileReference section */ 16CD00BB887C10CE3E91B52C /* Pods_MapPing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MapPing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4BFA57CDB381CDA05A7C2162 /* Pods-MapPing.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MapPing.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MapPing/Pods-MapPing.debug.xcconfig"; sourceTree = ""; }; + 665624232067128700F37197 /* HttpService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpService.swift; sourceTree = ""; }; + 6656242720671A7900F37197 /* FailableDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailableDecodable.swift; sourceTree = ""; }; + 6656242920671BE300F37197 /* PartType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartType.swift; sourceTree = ""; }; + 6656242C20672F4900F37197 /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = ""; }; + 6656242E20672F5600F37197 /* DetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsViewController.swift; sourceTree = ""; }; + 93695A4B20671488007D1410 /* PartClusterAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartClusterAnnotationView.swift; sourceTree = ""; }; E901EFF955E9ACF4A910DAE7 /* Pods-MapPing.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MapPing.release.xcconfig"; path = "Pods/Target Support Files/Pods-MapPing/Pods-MapPing.release.xcconfig"; sourceTree = ""; }; F1196A75206481B60084672D /* PartAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartAnnotationView.swift; sourceTree = ""; }; F1196A772064857D0084672D /* PartAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartAnnotation.swift; sourceTree = ""; }; @@ -89,6 +101,15 @@ name = Pods; sourceTree = ""; }; + 6656242B20672F0000F37197 /* Details */ = { + isa = PBXGroup; + children = ( + 6656242E20672F5600F37197 /* DetailsViewController.swift */, + 6656242C20672F4900F37197 /* DetailsView.swift */, + ); + path = Details; + sourceTree = ""; + }; 6DAB11BEC4EE23ADF3FF7B54 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -101,6 +122,7 @@ isa = PBXGroup; children = ( F1196A75206481B60084672D /* PartAnnotationView.swift */, + 93695A4B20671488007D1410 /* PartClusterAnnotationView.swift */, ); path = Subviews; sourceTree = ""; @@ -143,6 +165,8 @@ isa = PBXGroup; children = ( F1196A8D20653B660084672D /* Part.swift */, + 6656242720671A7900F37197 /* FailableDecodable.swift */, + 6656242920671BE300F37197 /* PartType.swift */, ); path = Model; sourceTree = ""; @@ -151,6 +175,7 @@ isa = PBXGroup; children = ( F1196A8F20653D4A0084672D /* PartService.swift */, + 665624232067128700F37197 /* HttpService.swift */, ); path = Service; sourceTree = ""; @@ -215,6 +240,7 @@ F19EA0A42061C56600FE9A5E /* UI */ = { isa = PBXGroup; children = ( + 6656242B20672F0000F37197 /* Details */, F19EA0B12061C68C00FE9A5E /* AugmentedReality */, F19EA0AA2061C5B400FE9A5E /* Common */, F19EA0AF2061C66800FE9A5E /* Map */, @@ -419,11 +445,16 @@ F19EA0AC2061C5BE00FE9A5E /* BaseViewController.swift in Sources */, F19EA0AE2061C5E800FE9A5E /* RootView.swift in Sources */, F19EA0C22061DA7E00FE9A5E /* Stylesheet.swift in Sources */, + 6656242820671A7900F37197 /* FailableDecodable.swift in Sources */, + 6656242A20671BE300F37197 /* PartType.swift in Sources */, F1196A92206541700084672D /* ServiceFactory.swift in Sources */, F1196A8420649D580084672D /* PartCellView.swift in Sources */, F19EA0A92061C59B00FE9A5E /* ViewControllerFactory.swift in Sources */, F1196A9020653D4A0084672D /* PartService.swift in Sources */, F19EA0902061C36000FE9A5E /* AppDelegate.swift in Sources */, + 6656242F20672F5600F37197 /* DetailsViewController.swift in Sources */, + 6656242D20672F4900F37197 /* DetailsView.swift in Sources */, + 93695A4C20671488007D1410 /* PartClusterAnnotationView.swift in Sources */, F19EA0B32061C6A800FE9A5E /* AugmentedRealityViewController.swift in Sources */, F1196A8E20653B660084672D /* Part.swift in Sources */, F19EA0C02061CB7100FE9A5E /* NavigationButtons.swift in Sources */, @@ -432,6 +463,7 @@ F19EA0A72061C58400FE9A5E /* RootViewController.swift in Sources */, F1196A76206481B60084672D /* PartAnnotationView.swift in Sources */, F19EA0B72061C6C100FE9A5E /* ListViewController.swift in Sources */, + 665624242067128700F37197 /* HttpService.swift in Sources */, F19EA0BB2061C6DC00FE9A5E /* MapView.swift in Sources */, F19EA0BD2061C6E300FE9A5E /* ListView.swift in Sources */, F19EA0B52061C6B300FE9A5E /* MapViewController.swift in Sources */, @@ -567,7 +599,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 4Q596JWQC5; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/MapPing/SupportingFiles/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.mirego.MapPing; @@ -583,7 +615,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 4Q596JWQC5; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/MapPing/SupportingFiles/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.mirego.MapPing; diff --git a/ios/MapPing/Classes/Domain/Model/FailableDecodable.swift b/ios/MapPing/Classes/Domain/Model/FailableDecodable.swift new file mode 100644 index 0000000..5f9ddf8 --- /dev/null +++ b/ios/MapPing/Classes/Domain/Model/FailableDecodable.swift @@ -0,0 +1,17 @@ +// +// FailableDecodable.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +struct FailableDecodable : Decodable { + + let base: Base? + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.base = try? container.decode(Base.self) + } +} diff --git a/ios/MapPing/Classes/Domain/Model/Part.swift b/ios/MapPing/Classes/Domain/Model/Part.swift index 9180818..dac5c04 100644 --- a/ios/MapPing/Classes/Domain/Model/Part.swift +++ b/ios/MapPing/Classes/Domain/Model/Part.swift @@ -8,11 +8,19 @@ struct Part: Codable { enum CodingKeys: String, CodingKey { case name + case component + case notes + case type case latitude = "lat" case longitude = "lon" + case address } let name: String - let latitude: Float - let longitude: Float + let component: String + let notes: String + let type: PartType + let latitude: Double + let longitude: Double + let address: String } diff --git a/ios/MapPing/Classes/Domain/Model/PartType.swift b/ios/MapPing/Classes/Domain/Model/PartType.swift new file mode 100644 index 0000000..70b4ec9 --- /dev/null +++ b/ios/MapPing/Classes/Domain/Model/PartType.swift @@ -0,0 +1,40 @@ +// +// PartType.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +enum PartType: String, Codable { + case battery + case bulb + case cable + case chain + case clutch + case coil + case dial + case disk + case fan + case filter + case fuel + case gear + case generator + case heads + case hose + case mag + case mount + case piston + case plugs + case radiator + case regulator + case reservoir + case scanner + case sensor + case shaft + case sink + case sparkplug + case turbo + case valve + case wheel +} diff --git a/ios/MapPing/Classes/Domain/Model/Parts.swift b/ios/MapPing/Classes/Domain/Model/Parts.swift new file mode 100644 index 0000000..f0dc05c --- /dev/null +++ b/ios/MapPing/Classes/Domain/Model/Parts.swift @@ -0,0 +1,12 @@ +// +// Parts.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +struct Parts: Codable { + let data: [Part] +} + diff --git a/ios/MapPing/Classes/Domain/Service/HttpService.swift b/ios/MapPing/Classes/Domain/Service/HttpService.swift new file mode 100644 index 0000000..e9dbe63 --- /dev/null +++ b/ios/MapPing/Classes/Domain/Service/HttpService.swift @@ -0,0 +1,23 @@ +// +// HttpService.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +import Foundation + +class HttpService { + + static func get(url: String, completion: @escaping (_ data: Data?, _ error: Error?) -> ()) { + guard let queryUrl = URL(string: url) else { + completion(nil, NSError(domain: "HttpService", code: -1, userInfo: ["description" : "Invalid url"])) + return + } + + URLSession.shared.dataTask(with: queryUrl) { (data, urlResponse, error) in + DispatchQueue.main.async { completion(data, error) } + }.resume() + } +} diff --git a/ios/MapPing/Classes/Domain/Service/PartService.swift b/ios/MapPing/Classes/Domain/Service/PartService.swift index a340aa3..62af7ff 100644 --- a/ios/MapPing/Classes/Domain/Service/PartService.swift +++ b/ios/MapPing/Classes/Domain/Service/PartService.swift @@ -5,15 +5,27 @@ // Copyright © 2018 Mirego. All rights reserved. // -import Foundation +import UIKit class PartService { - private let partsUrl = URL(string: "https://s3.amazonaws.com/shared.ws.mirego.com/competition/mapping.json")! + let partsUrl = "https://s3.amazonaws.com/shared.ws.mirego.com/competition/mapping.json" var partsObservable = Observable<[Part]>() func refreshParts() { - partsObservable.notify(data: []) - // TODO 🙄 + HttpService.get(url: partsUrl) { (data, error) in + guard let data = data else { + return + } + do { + let partsData = try JSONDecoder() + .decode([FailableDecodable].self, from: data) + .flatMap { $0.base } + + self.partsObservable.notify(data:partsData) + } catch { + print("Error trying to convert data to JSON") + } + } } } diff --git a/ios/MapPing/Classes/UI/Details/DetailsView.swift b/ios/MapPing/Classes/UI/Details/DetailsView.swift new file mode 100644 index 0000000..1d16711 --- /dev/null +++ b/ios/MapPing/Classes/UI/Details/DetailsView.swift @@ -0,0 +1,80 @@ +// +// DetailsView.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +import UIKit + +class DetailsView: UIView { + + private let partImage = UIImageView() + private let title = UILabel() + private let subTitle = UILabel() + private let coordinates = UILabel() + private let distance = UILabel() + private let address = UILabel() + private let notes = UILabel() + + init() { + super.init(frame: .zero) + backgroundColor = .white + + partImage.backgroundColor = .white + partImage.contentMode = .center + partImage.size = CGSize(width: 66, height: 66) + partImage.layer.cornerRadius = partImage.height / 2 + partImage.layer.borderColor = UIColor.copper.cgColor + partImage.layer.borderWidth = 4 + addSubview(partImage) + + title.setProperties(font: .leagueSpartanBold(14), textColor: .purpleBrown) + addSubview(title) + + subTitle.setProperties(font: .systemFont(ofSize: 12), textColor: .purpleBrown) + addSubview(subTitle) + + coordinates.setProperties(font: .systemFont(ofSize: 12), textColor: .brownishGrey) + addSubview(coordinates) + + distance.setProperties(font: .italicSystemFont(ofSize: 12), textColor: .brownishGrey) + addSubview(distance) + + address.setProperties(font: .italicSystemFont(ofSize: 12), textColor: .brownishGrey) + addSubview(address) + + notes.setProperties(font: .italicSystemFont(ofSize: 12), textColor: .brownishGrey) + addSubview(notes) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + partImage.pin.left(15).vCenter() + title.pin.right(of: partImage, aligned: .top).marginLeft(15).marginTop(8) + subTitle.pin.below(of: title, aligned: .left).marginTop(1) + coordinates.pin.below(of: subTitle, aligned: .left).marginTop(5) + distance.pin.right(of: coordinates, aligned: .center).marginLeft(6) + address.pin.below(of: partImage, aligned: .left).marginTop(8) + notes.pin.below(of: address, aligned: .left).marginTop(8) + } + + func configure(part: Part) { + let coordinates = "\(part.latitude)° N, \(part.longitude)° W"; + + partImage.image = UIImage(named: "part-\(part.type)") + self.title.setProperties(text: part.name, fit: true) + self.subTitle.setProperties(text: part.component, fit: true) + self.coordinates.setProperties(text: coordinates, fit: true) + self.distance.setProperties(text: "(0.62 km)", fit: true) + self.coordinates.setProperties(text: coordinates, fit: true) + self.address.setProperties(text: part.address, fit: true) + self.notes.setProperties(text: part.notes, fit: true) + } +} diff --git a/ios/MapPing/Classes/UI/Details/DetailsViewController.swift b/ios/MapPing/Classes/UI/Details/DetailsViewController.swift new file mode 100644 index 0000000..86420ce --- /dev/null +++ b/ios/MapPing/Classes/UI/Details/DetailsViewController.swift @@ -0,0 +1,42 @@ +// +// DetailsViewController.swift +// MapPing +// +// Created by Quentin Nolan on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +import UIKit + +class DetailsViewController: BaseViewController { + + private var mainView: DetailsView { + return self.view as! DetailsView + } + + private let part: Part + + init(part: Part) { + self.part = part + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + override func loadView() { + view = DetailsView() + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.mainView.configure(part: part) + } + +} diff --git a/ios/MapPing/Classes/UI/List/ListView.swift b/ios/MapPing/Classes/UI/List/ListView.swift index a421c66..6f6a0f3 100644 --- a/ios/MapPing/Classes/UI/List/ListView.swift +++ b/ios/MapPing/Classes/UI/List/ListView.swift @@ -8,15 +8,39 @@ import UIKit class ListView: UIView { - + + private var parts: [Part] = [] private let partCellView = PartCellView() + + fileprivate var calloutDetailsPressed: ((Part) -> ())? = nil + + fileprivate lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsets.zero + layout.minimumInteritemSpacing = 0 + layout.minimumLineSpacing = 0 + return layout + }() + + fileprivate lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0), collectionViewLayout: self.collectionViewLayout) + collectionView.backgroundColor = UIColor.clear + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + + let cellMargin: CGFloat = 1 + var cellDimensions: CGSize { + return CGSize(width: UIScreen.main.bounds.width, height: 100) + } init() { super.init(frame: .zero) backgroundColor = .white - - partCellView.configure(partImageName: "part-sensor", title: "Bougie 4W", subTitle: "Moteur principal", coordinates: "46.7552° N, 71.2265° W", distance: "(0.62 km)") - addSubview(partCellView) + + collectionView.register(PartCellView.self, forCellWithReuseIdentifier: PartCellView.reuseIdentifier) + addSubview(collectionView) } required init(coder aDecoder: NSCoder) { @@ -25,6 +49,55 @@ class ListView: UIView { override func layoutSubviews() { super.layoutSubviews() - partCellView.pin.top().horizontally() + + collectionView.frame.origin.x = 0 - cellMargin + collectionView.frame.size.width = self.frame.size.width + cellMargin*2 + collectionView.frame.size.height = self.frame.size.height + collectionView.frame.origin.y = 0 + } + + func configure(parts: [Part], onDetailPressed: ((Part) -> ())? = nil) { + self.parts = parts + collectionView.reloadData() + self.calloutDetailsPressed = onDetailPressed + } + + func calloutDetailPressed(part: Part) { + calloutDetailsPressed?(part) + } +} + +extension ListView: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return parts.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PartCellView.reuseIdentifier, for: indexPath) as! PartCellView + cell.configure(part: parts[indexPath.row]) + return cell + } +} + +extension ListView: UICollectionViewDelegateFlowLayout, UICollectionViewDelegate +{ + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return cellDimensions + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return cellMargin*2 + 1.5 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return cellMargin*2 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsetsMake(cellMargin, cellMargin, cellMargin, cellMargin) // margin between cells + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + calloutDetailPressed(part: parts[(indexPath as NSIndexPath).row]); } } diff --git a/ios/MapPing/Classes/UI/List/ListViewController.swift b/ios/MapPing/Classes/UI/List/ListViewController.swift index e1daee2..d388ab0 100644 --- a/ios/MapPing/Classes/UI/List/ListViewController.swift +++ b/ios/MapPing/Classes/UI/List/ListViewController.swift @@ -37,8 +37,13 @@ class ListViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - _ = partService.partsObservable.register { (_, parts) in + _ = partService.partsObservable.register { [weak self] (_, parts) in print("Nb of parts received: \(parts.count)") + + self?.mainView.configure(parts: parts) { (part) in + let detailsViewControlller = DetailsViewController(part: part) + self?.navigationController?.pushViewController(detailsViewControlller, animated: true) + } } } } diff --git a/ios/MapPing/Classes/UI/List/Subviews/PartCellView.swift b/ios/MapPing/Classes/UI/List/Subviews/PartCellView.swift index eafa994..90029a4 100644 --- a/ios/MapPing/Classes/UI/List/Subviews/PartCellView.swift +++ b/ios/MapPing/Classes/UI/List/Subviews/PartCellView.swift @@ -7,7 +7,8 @@ import UIKit -class PartCellView: UIView { +class PartCellView: UICollectionViewCell { + static let reuseIdentifier = "PartCellView" private let partImage = UIImageView() private let title = UILabel() @@ -15,8 +16,8 @@ class PartCellView: UIView { private let coordinates = UILabel() private let distance = UILabel() - init() { - super.init(frame: .zero) + override init(frame: CGRect) { + super.init(frame: frame) partImage.backgroundColor = .white partImage.contentMode = .center @@ -55,12 +56,14 @@ class PartCellView: UIView { distance.pin.right(of: coordinates, aligned: .center).marginLeft(6) } - func configure(partImageName: String, title: String, subTitle: String, coordinates: String, distance: String) { - partImage.image = UIImage(named: partImageName) - self.title.setProperties(text: title, fit: true) - self.subTitle.setProperties(text: subTitle, fit: true) + func configure(part: Part) { + let coordinates = "\(part.latitude)° N, \(part.longitude)° W"; + + partImage.image = UIImage(named: "part-\(part.type)") + self.title.setProperties(text: part.name, fit: true) + self.subTitle.setProperties(text: part.component, fit: true) self.coordinates.setProperties(text: coordinates, fit: true) - self.distance.setProperties(text: distance, fit: true) + self.distance.setProperties(text: "(0.62 km)", fit: true) setNeedsLayout() } } diff --git a/ios/MapPing/Classes/UI/Map/MapView.swift b/ios/MapPing/Classes/UI/Map/MapView.swift index 9496145..d3c7116 100644 --- a/ios/MapPing/Classes/UI/Map/MapView.swift +++ b/ios/MapPing/Classes/UI/Map/MapView.swift @@ -32,9 +32,27 @@ class MapView: UIView { extension MapView: MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { guard !(annotation is MKUserLocation), let annotation = annotation as? PartAnnotation else { return nil } - + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: PartAnnotationView.reuseIdentifier, for: annotation) as! PartAnnotationView - annotationView.configure(partImageName: annotation.iconName) + annotationView.configure(partAnnotation: annotation) { + // TODO + } return annotationView + + } + + func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + guard let annotationView = view as? PartAnnotationView else { return } + + annotationView.didSelect() + if let location = annotationView.annotation?.coordinate { + mapView.setCenter(location, animated: true) + } + } + + func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { + guard let annotationView = view as? PartAnnotationView else { return } + + annotationView.didDeselect() } } diff --git a/ios/MapPing/Classes/UI/Map/MapViewController.swift b/ios/MapPing/Classes/UI/Map/MapViewController.swift index ad7d72e..2f0a467 100644 --- a/ios/MapPing/Classes/UI/Map/MapViewController.swift +++ b/ios/MapPing/Classes/UI/Map/MapViewController.swift @@ -16,7 +16,10 @@ class MapViewController: BaseViewController { return self.view as! MapView } - init() { + private let partService: PartService + + init(partService: PartService) { + self.partService = partService super.init(nibName: nil, bundle: nil) navigationIcon = #imageLiteral(resourceName: "icn-map") } @@ -32,6 +35,14 @@ class MapViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() mainView.mapView.setRegion(MKCoordinateRegion(center: quebecCityCoordinate, span: startSpan), animated: false) - mainView.mapView.addAnnotation(PartAnnotation(coordinate: quebecCityCoordinate, iconName: "part-clutch")) + + _ = partService.partsObservable.register { [weak self] (_, parts) in + let partAnnotations = parts.map { + PartAnnotation(part: $0) + } + for part in partAnnotations { + self?.mainView.mapView.addAnnotation(part) + } + } } } diff --git a/ios/MapPing/Classes/UI/Map/PartAnnotation.swift b/ios/MapPing/Classes/UI/Map/PartAnnotation.swift index 6dbf4c5..e003815 100644 --- a/ios/MapPing/Classes/UI/Map/PartAnnotation.swift +++ b/ios/MapPing/Classes/UI/Map/PartAnnotation.swift @@ -10,9 +10,15 @@ import MapKit class PartAnnotation: NSObject, MKAnnotation { let coordinate: CLLocationCoordinate2D let iconName: String + let name: String + + var title: String? { + return name + } - init(coordinate: CLLocationCoordinate2D, iconName: String) { - self.coordinate = coordinate - self.iconName = iconName + init(part: Part) { + self.coordinate = CLLocationCoordinate2DMake(part.latitude, part.longitude) + iconName = "part-\(part.type)" + self.name = part.name } } diff --git a/ios/MapPing/Classes/UI/Map/Subviews/PartAnnotationView.swift b/ios/MapPing/Classes/UI/Map/Subviews/PartAnnotationView.swift index d881f7a..713e18e 100644 --- a/ios/MapPing/Classes/UI/Map/Subviews/PartAnnotationView.swift +++ b/ios/MapPing/Classes/UI/Map/Subviews/PartAnnotationView.swift @@ -11,14 +11,30 @@ class PartAnnotationView: MKAnnotationView { static let reuseIdentifier = "PartAnnotationView" private let pinImage = UIImageView(image: #imageLiteral(resourceName: "icn-pin")) + private let partCallout = UIView() + private let partName = UILabel() private let partImage = UIImageView() + private let calloutButton = UIButton(type: .detailDisclosure) + + private let calloutDetailsPressed: (() -> ())? = nil override init(annotation: MKAnnotation?, reuseIdentifier: String?) { super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) addSubview(pinImage) + addSubview(partCallout) addSubview(partImage) + partName.adjustsFontSizeToFitWidth = true + partCallout.addSubview(partName) + partCallout.addSubview(calloutButton) + calloutButton.addTarget(self, action: #selector(calloutDetailPressed), for: .touchUpInside) size = pinImage.size + partCallout.isHidden = true + } + + @objc + func calloutDetailPressed() { + calloutDetailsPressed?() } required init?(coder aDecoder: NSCoder) { @@ -28,10 +44,26 @@ class PartAnnotationView: MKAnnotationView { override func layoutSubviews() { super.layoutSubviews() partImage.pin.hCenter().top(11).size(CGSize(width: 22, height: 22)) + partCallout.pin.hCenter().top(-64).size(CGSize(width: 258, height: 48)) + partCallout.setRoundCornersMask() + partCallout.backgroundColor = UIColor(hex: 0x000000, alpha: 0.7) + partName.pin.vCenter().start(8).height(32).before(of: calloutButton).marginRight(8) + partName.textColor = UIColor.white + calloutButton.pin.right(8).vCenter() } - func configure(partImageName: String) { - partImage.image = UIImage(named: partImageName) + func configure(partAnnotation: PartAnnotation, onDetailPressed: (() -> ())? = nil) { + partImage.image = UIImage(named: partAnnotation.iconName) + partName.text = partAnnotation.name setNeedsLayout() } + + func didSelect() { + partCallout.isHidden = false + } + + func didDeselect() { + partCallout.isHidden = true + } + } diff --git a/ios/MapPing/Classes/UI/Map/Subviews/PartClusterAnnotationView.swift b/ios/MapPing/Classes/UI/Map/Subviews/PartClusterAnnotationView.swift new file mode 100644 index 0000000..97f2923 --- /dev/null +++ b/ios/MapPing/Classes/UI/Map/Subviews/PartClusterAnnotationView.swift @@ -0,0 +1,39 @@ +// +// PartClusterAnnotationView.swift +// MapPing +// +// Created by Marc-Antoine on 18-03-24. +// Copyright © 2018 Mirego. All rights reserved. +// + +import MapKit + +class PartClusterAnnotationView: MKAnnotationView { + static let reuseIdentifier = "PartAnnotationView" + + private let pinImage = UIImageView(image: #imageLiteral(resourceName: "icn-pin")) + private let partImage = UIImageView() + + override init(annotation: MKAnnotation?, reuseIdentifier: String?) { + super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) + + addSubview(pinImage) + addSubview(partImage) + size = pinImage.size + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + partImage.pin.hCenter().top(11).size(CGSize(width: 22, height: 22)) + } + + func configure(partImageName: String) { + partImage.image = UIImage(named: partImageName) + setNeedsLayout() + } +} + diff --git a/ios/MapPing/Classes/UI/ViewControllerFactory.swift b/ios/MapPing/Classes/UI/ViewControllerFactory.swift index 21788a4..168d4e5 100644 --- a/ios/MapPing/Classes/UI/ViewControllerFactory.swift +++ b/ios/MapPing/Classes/UI/ViewControllerFactory.swift @@ -19,7 +19,7 @@ class ViewControllerFactory { } private func mapViewController() -> MapViewController { - return assign(MapViewController()) + return assign(MapViewController(partService: serviceFactory.partService())) } private func listViewController() -> NavigationViewController {