Skip to content

Commit

Permalink
Places Picker, Geocoder and basic layout
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrbernad committed Jul 7, 2019
1 parent cbe5a6b commit da42611
Show file tree
Hide file tree
Showing 25 changed files with 1,256 additions and 9 deletions.
22 changes: 22 additions & 0 deletions PlacePicker-iOS/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
19 changes: 19 additions & 0 deletions PlacePicker-iOS/PlacePicker_iOS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// PlacePicker_iOS.h
// PlacePicker-iOS
//
// Created by Piotr Bernad on 04/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

#import <UIKit/UIKit.h>

//! Project version number for PlacePicker_iOS.
FOUNDATION_EXPORT double PlacePicker_iOSVersionNumber;

//! Project version string for PlacePicker_iOS.
FOUNDATION_EXPORT const unsigned char PlacePicker_iOSVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <PlacePicker_iOS/PublicHeader.h>


22 changes: 22 additions & 0 deletions PlacePicker-iOSTests/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
34 changes: 34 additions & 0 deletions PlacePicker-iOSTests/PlacePicker_iOSTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// PlacePicker_iOSTests.swift
// PlacePicker-iOSTests
//
// Created by Piotr Bernad on 04/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import XCTest
@testable import PlacePicker_iOS

class PlacePicker_iOSTests: XCTestCase {

override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}

func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}

}
391 changes: 387 additions & 4 deletions PlacesPicker.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<dict>
<key>SchemeUserState</key>
<dict>
<key>PlacePicker-iOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>PlacesPicker.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
Expand Down
25 changes: 21 additions & 4 deletions PlacesPicker/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="PlacesPicker" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wb2-oj-2JZ">
<rect key="frame" x="165" y="433" width="85" height="30"/>
<state key="normal" title="Select Place"/>
<connections>
<action selector="showPlacePicker:" destination="BYZ-38-t0r" eventType="touchUpInside" id="bue-4x-yeX"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="wb2-oj-2JZ" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="UfR-yi-BK6"/>
<constraint firstItem="wb2-oj-2JZ" firstAttribute="centerY" secondItem="6Tk-OE-BBY" secondAttribute="centerY" id="r7M-73-QqY"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
Expand Down
47 changes: 47 additions & 0 deletions PlacesPicker/Classes/DefaultPlacesListRenderer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// DefaultListRenderer.swift
// PlacePicker-iOS
//
// Created by Piotr Bernad on 07/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation

public class DefaultPlacesListRenderer: PlacesListRenderer {
public init() {}

public func registerCells(tableView: UITableView) {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}

public func cellForRowAt(indexPath: IndexPath, tableView: UITableView, object: PlacesListObjectType) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.numberOfLines = 0

switch object {
case .address(let address):
cell.textLabel?.text = address.formattedAddress
cell.accessoryType = .disclosureIndicator
return cell
case .error(let error):
cell.textLabel?.text = error.localizedDescription
cell.accessoryType = .none
return cell
case .loading:
cell.textLabel?.text = NSLocalizedString("Loading", comment: "")
cell.accessoryType = .none
return cell
case .nothingSelected:
cell.textLabel?.text = NSLocalizedString("Please select location on the map or use search.", comment: "")
cell.accessoryType = .none
return cell
case .place(let place):
cell.textLabel?.text = [place.name, place.formattedAddress].compactMap { $0 }.joined(separator: ", ")
cell.accessoryType = .none
return cell
}
}


}
67 changes: 67 additions & 0 deletions PlacesPicker/Classes/Geocoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Geocoder.swift
// PlacesPicker
//
// Created by Piotr Bernad on 07/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation
import GoogleMaps

public enum GeocoderError: Error {
case couldNotBuildRequest
}

public class Geocoder {
private let baseUrl: URL = URL(string: "https://maps.googleapis.com/maps/api/geocode/json")!
private let apiKey: String

public init() {
guard let googleMapsKey = PlacePicker.googleMapsKey else {
fatalError("Missing Google Maps Key. Please call PlacePicker.configure() before using Picker.")
}
self.apiKey = googleMapsKey
}

public func reverseGeocode(coordinate: CLLocationCoordinate2D, completion: @escaping (ReverseGeocodeResponse?, Error?) -> ()) {

var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)
components?.queryItems = [URLQueryItem(name: "latlng", value: "\(coordinate.latitude),\(coordinate.longitude)"),
URLQueryItem(name: "key", value: apiKey)]

guard let url = components?.url else {
completion(nil, GeocoderError.couldNotBuildRequest)
return
}

perfromGeocodeRequest(url: url, completion: completion)

}

private func perfromGeocodeRequest(url: URL, completion: @escaping (ReverseGeocodeResponse?, Error?) -> ()) {
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil, error)
}
return
}

do {
let jsonDecoder = JSONDecoder()
let decodedResponse = try jsonDecoder.decode(ReverseGeocodeResponse.self, from: data)
DispatchQueue.main.async {
completion(decodedResponse, nil)
}
} catch let error {
DispatchQueue.main.async {
completion(nil, error)
}
}

}

task.resume()
}
}
19 changes: 19 additions & 0 deletions PlacesPicker/Classes/ListState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ListState.swift
// PlacePicker-iOS
//
// Created by Piotr Bernad on 07/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation
import GooglePlaces
import GoogleMaps

public enum ListState {
case nothingSelected
case loading
case singlePlace(place: GMSPlace)
case error(error: Error)
case adresses(objects: [ReverseGeocodeResult])
}
33 changes: 33 additions & 0 deletions PlacesPicker/Classes/PickerRenderer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// PickerRenderer.swift
// PlacePicker-iOS
//
// Created by Piotr Bernad on 07/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation
import GoogleMaps

public protocol PickerRenderer {
func configureCancelButton(barButtonItem: UIBarButtonItem)
func configureSearchButton(barButtonItem: UIBarButtonItem)
func configureMapView(mapView: GMSMapView)
func configureTableView(mapView: UITableView)
}

public class DefaultPickerRenderer: PickerRenderer {
public init() { }

public func configureCancelButton(barButtonItem: UIBarButtonItem) {
}

public func configureSearchButton(barButtonItem: UIBarButtonItem) {
}

public func configureMapView(mapView: GMSMapView) {
}

public func configureTableView(mapView: UITableView) {
}
}
25 changes: 25 additions & 0 deletions PlacesPicker/Classes/PlacePicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// PlacePicker.swift
// PlacePicker-iOS
//
// Created by Piotr Bernad on 04/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation
import GoogleMaps
import GooglePlaces

public class PlacePicker {
static var googleMapsKey: String?

public static func configure(googleMapsAPIKey: String, placesAPIKey: String) {
GMSServices.provideAPIKey(googleMapsAPIKey)
GMSPlacesClient.provideAPIKey(placesAPIKey)
googleMapsKey = googleMapsAPIKey
}

public static func placePickerController(config: PlacePickerConfig = PlacePickerConfig.default) -> PlacePickerController {
return PlacePickerController.controler(config: config)
}
}
43 changes: 43 additions & 0 deletions PlacesPicker/Classes/PlacePickerConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// PlacePickerConfig.swift
// PlacePicker-iOS
//
// Created by Piotr Bernad on 05/07/2019.
// Copyright © 2019 Piotr Bernad. All rights reserved.
//

import Foundation
import GooglePlaces

public struct PlacePickerConfig {
public let listRenderer: PlacesListRenderer
public let placeFields: GMSPlaceField
public let pickerRenderer: PickerRenderer
public let placesFilter: GMSAutocompleteFilter?

public static var `default`: PlacePickerConfig {
return PlacePickerConfig()
}

public init(listRenderer: PlacesListRenderer = DefaultPlacesListRenderer(),
placeFields: GMSPlaceField = GMSPlaceField.defaultFields,
placesFilter: GMSAutocompleteFilter? = nil,
pickerRenderer: PickerRenderer = DefaultPickerRenderer()) {
self.listRenderer = listRenderer
self.placeFields = placeFields
self.placesFilter = placesFilter
self.pickerRenderer = pickerRenderer
}
}

extension GMSPlaceField {
public static var defaultFields: GMSPlaceField {
return GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) |
UInt(GMSPlaceField.addressComponents.rawValue) |
UInt(GMSPlaceField.coordinate.rawValue) |
UInt(GMSPlaceField.formattedAddress.rawValue) |
UInt(GMSPlaceField.photos.rawValue))!
}
}

Loading

0 comments on commit da42611

Please sign in to comment.