ConfigurationViewController.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Copyright 2021 Google LLC. All rights reserved.
  2. //
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  5. // file except in compliance with the License. You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software distributed under
  10. // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. // ANY KIND, either express or implied. See the License for the specific language governing
  12. // permissions and limitations under the License.
  13. import GooglePlaces
  14. import UIKit
  15. /// The section of configuration.
  16. struct ConfigSection {
  17. let name: String
  18. let samples: [ConfigData]
  19. }
  20. /// The configuration data.
  21. struct ConfigData {
  22. let name: String
  23. let tag: Int
  24. let action: Selector
  25. }
  26. /// The location option for autocomplete search.
  27. enum LocationOption: Int {
  28. case unspecified = 100
  29. case canada = 101
  30. case kansas = 102
  31. var northEast: CLLocationCoordinate2D {
  32. switch self {
  33. case .canada:
  34. return CLLocationCoordinate2D(latitude: 70.0, longitude: -60.0)
  35. case .kansas:
  36. return CLLocationCoordinate2D(latitude: 39.0, longitude: -95.0)
  37. default:
  38. return CLLocationCoordinate2D()
  39. }
  40. }
  41. var southWest: CLLocationCoordinate2D {
  42. switch self {
  43. case .canada:
  44. return CLLocationCoordinate2D(latitude: 50.0, longitude: -140.0)
  45. case .kansas:
  46. return CLLocationCoordinate2D(latitude: 37.5, longitude: -100.0)
  47. default:
  48. return CLLocationCoordinate2D()
  49. }
  50. }
  51. }
  52. /// Manages the configuration options view for the demo app.
  53. class ConfigurationViewController: UIViewController {
  54. // MARK: - Properties
  55. private let cellIdentifier = "cellIdentifier"
  56. private let filterTagBase = 1000
  57. private lazy var configurationSections: [ConfigSection] = {
  58. var sections: [ConfigSection] = []
  59. let autocompleteFiltersSelector = #selector(autocompleteFiltersSwitch)
  60. let geocode = ConfigData(
  61. name: "Geocode", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.geocode.rawValue,
  62. action: autocompleteFiltersSelector)
  63. let address = ConfigData(
  64. name: "Address", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.address.rawValue,
  65. action: autocompleteFiltersSelector)
  66. let establishment = ConfigData(
  67. name: "Establishment",
  68. tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.establishment.rawValue,
  69. action: autocompleteFiltersSelector)
  70. let region = ConfigData(
  71. name: "Region", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.region.rawValue,
  72. action: autocompleteFiltersSelector)
  73. let city = ConfigData(
  74. name: "City", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.city.rawValue,
  75. action: autocompleteFiltersSelector)
  76. sections.append(
  77. ConfigSection(
  78. name: "Autocomplete filters", samples: [geocode, address, establishment, region, city]))
  79. let canada = ConfigData(
  80. name: "Canada", tag: LocationOption.canada.rawValue, action: #selector(canadaSwitch))
  81. let kansas = ConfigData(
  82. name: "Kansas", tag: LocationOption.kansas.rawValue, action: #selector(kansasSwitch))
  83. sections.append(
  84. ConfigSection(name: "Autocomplete Restriction Bounds", samples: [canada, kansas]))
  85. let placesFieldsSelector = #selector(placesFieldsSwitch)
  86. let name = ConfigData(
  87. name: "Name", tag: Int(GMSPlaceField.name.rawValue), action: placesFieldsSelector)
  88. let placeId = ConfigData(
  89. name: "Place ID", tag: Int(GMSPlaceField.placeID.rawValue),
  90. action: placesFieldsSelector)
  91. let plusCode = ConfigData(
  92. name: "Plus Code", tag: Int(GMSPlaceField.plusCode.rawValue),
  93. action: placesFieldsSelector)
  94. let coordinate = ConfigData(
  95. name: "Coordinate", tag: Int(GMSPlaceField.coordinate.rawValue),
  96. action: placesFieldsSelector)
  97. let openingHours = ConfigData(
  98. name: "Opening Hours", tag: Int(GMSPlaceField.openingHours.rawValue),
  99. action: placesFieldsSelector)
  100. let phoneNumber = ConfigData(
  101. name: "Phone Number", tag: Int(GMSPlaceField.phoneNumber.rawValue),
  102. action: placesFieldsSelector)
  103. let formattedAddress = ConfigData(
  104. name: "Formatted Address", tag: Int(GMSPlaceField.formattedAddress.rawValue),
  105. action: placesFieldsSelector)
  106. let rating = ConfigData(
  107. name: "Rating", tag: Int(GMSPlaceField.rating.rawValue), action: placesFieldsSelector)
  108. let ratingsTotal = ConfigData(
  109. name: "User Ratings Total", tag: Int(GMSPlaceField.userRatingsTotal.rawValue),
  110. action: placesFieldsSelector)
  111. let priceLevel = ConfigData(
  112. name: "Price Level", tag: Int(GMSPlaceField.priceLevel.rawValue),
  113. action: placesFieldsSelector)
  114. let types = ConfigData(
  115. name: "Types", tag: Int(GMSPlaceField.types.rawValue), action: placesFieldsSelector)
  116. let website = ConfigData(
  117. name: "Website", tag: Int(GMSPlaceField.website.rawValue),
  118. action: placesFieldsSelector)
  119. let viewPort = ConfigData(
  120. name: "Viewport", tag: Int(GMSPlaceField.viewport.rawValue),
  121. action: placesFieldsSelector)
  122. let addressComponents = ConfigData(
  123. name: "Address Components", tag: Int(GMSPlaceField.addressComponents.rawValue),
  124. action: placesFieldsSelector)
  125. let photos = ConfigData(
  126. name: "Photos", tag: Int(GMSPlaceField.photos.rawValue), action: placesFieldsSelector)
  127. let minutes = ConfigData(
  128. name: "UTC Offset Minutes", tag: Int(GMSPlaceField.utcOffsetMinutes.rawValue),
  129. action: placesFieldsSelector)
  130. let status = ConfigData(
  131. name: "Business Status", tag: Int(GMSPlaceField.businessStatus.rawValue),
  132. action: placesFieldsSelector)
  133. let iconImageURL = ConfigData(
  134. name: "Icon Image URL", tag: Int(GMSPlaceField.iconImageURL.rawValue),
  135. action: placesFieldsSelector)
  136. let iconBackgroundColor = ConfigData(
  137. name: "Icon Background Color", tag: Int(GMSPlaceField.iconBackgroundColor.rawValue),
  138. action: placesFieldsSelector)
  139. sections.append(
  140. ConfigSection(
  141. name: "Place Fields",
  142. samples: [
  143. name, placeId, plusCode, coordinate, openingHours, phoneNumber, formattedAddress, rating,
  144. ratingsTotal, priceLevel, types, website, viewPort, addressComponents, photos, minutes,
  145. status, iconImageURL, iconBackgroundColor,
  146. ]))
  147. return sections
  148. }()
  149. private var configuration: AutocompleteConfiguration
  150. private lazy var tableView: UITableView = {
  151. let tableView = UITableView()
  152. tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
  153. tableView.dataSource = self
  154. tableView.delegate = self
  155. tableView.translatesAutoresizingMaskIntoConstraints = false
  156. return tableView
  157. }()
  158. private lazy var closeButton: UIButton = {
  159. let button = UIButton()
  160. button.backgroundColor = .blue
  161. button.setTitle("Close", for: .normal)
  162. button.setTitleColor(.white, for: .normal)
  163. button.translatesAutoresizingMaskIntoConstraints = false
  164. button.addTarget(self, action: #selector(tapCloseButton(_:)), for: .touchUpInside)
  165. return button
  166. }()
  167. // MARK: - Public functions
  168. public init(configuration: AutocompleteConfiguration) {
  169. self.configuration = configuration
  170. super.init(nibName: nil, bundle: nil)
  171. }
  172. required init(coder: NSCoder) { fatalError() }
  173. override func viewDidLoad() {
  174. super.viewDidLoad()
  175. view.addSubview(tableView)
  176. view.addSubview(closeButton)
  177. let guide = view.safeAreaLayoutGuide
  178. NSLayoutConstraint.activate([
  179. tableView.topAnchor.constraint(equalTo: guide.topAnchor),
  180. tableView.bottomAnchor.constraint(equalTo: closeButton.topAnchor),
  181. tableView.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
  182. tableView.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
  183. ])
  184. NSLayoutConstraint.activate([
  185. closeButton.bottomAnchor.constraint(equalTo: guide.bottomAnchor),
  186. closeButton.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
  187. closeButton.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
  188. ])
  189. }
  190. // MARK: - Private functions
  191. @objc private func tapCloseButton(_ sender: UISwitch) {
  192. dismiss(animated: true)
  193. guard let location = configuration.location else { return }
  194. let northEast = location.northEast
  195. let southWest = location.southWest
  196. // Update configuration
  197. switch location {
  198. case .canada:
  199. configuration.autocompleteFilter.origin = CLLocation(
  200. latitude: northEast.latitude, longitude: northEast.longitude)
  201. configuration.autocompleteFilter.locationRestriction =
  202. GMSPlaceRectangularLocationOption(northEast, southWest)
  203. case .kansas:
  204. configuration.autocompleteFilter.origin = CLLocation(
  205. latitude: northEast.latitude, longitude: northEast.longitude)
  206. configuration.autocompleteFilter.locationRestriction =
  207. GMSPlaceRectangularLocationOption(northEast, southWest)
  208. default:
  209. configuration.autocompleteFilter.origin = nil
  210. configuration.autocompleteFilter.locationRestriction = nil
  211. }
  212. }
  213. @objc private func autocompleteFiltersSwitch(_ sender: UISwitch) {
  214. for sample in configurationSections[0].samples {
  215. guard let switchView = view.viewWithTag(sample.tag) as? UISwitch else { continue }
  216. if switchView.tag != sender.tag {
  217. switchView.setOn(false, animated: true)
  218. }
  219. }
  220. // The value of the type is tag - filterTagBase
  221. guard let type = GMSPlacesAutocompleteTypeFilter(rawValue: sender.tag - filterTagBase) else {
  222. return
  223. }
  224. configuration.autocompleteFilter.type = type
  225. }
  226. @objc private func canadaSwitch(_ sender: UISwitch) {
  227. if sender.isOn {
  228. // Turn off the Kansas switch
  229. guard let switchView = view.viewWithTag(LocationOption.kansas.rawValue) as? UISwitch else {
  230. return
  231. }
  232. switchView.setOn(false, animated: true)
  233. configuration.location = .canada
  234. } else {
  235. configuration.location = .unspecified
  236. }
  237. }
  238. @objc private func kansasSwitch(_ sender: UISwitch) {
  239. if sender.isOn {
  240. // Turn off the Canada switch
  241. guard let switchView = view.viewWithTag(LocationOption.canada.rawValue) as? UISwitch else {
  242. return
  243. }
  244. switchView.setOn(false, animated: true)
  245. configuration.location = .kansas
  246. } else {
  247. configuration.location = .unspecified
  248. }
  249. }
  250. @objc private func placesFieldsSwitch(_ sender: UISwitch) {
  251. var field = UInt(sender.tag)
  252. if sender.isOn {
  253. field |= configuration.placeFields.rawValue
  254. } else {
  255. field = ~field & configuration.placeFields.rawValue
  256. }
  257. configuration.placeFields = GMSPlaceField(rawValue: field)
  258. }
  259. }
  260. extension ConfigurationViewController: UITableViewDataSource {
  261. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  262. guard section <= configurationSections.count else {
  263. return 0
  264. }
  265. return configurationSections[section].samples.count
  266. }
  267. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
  268. -> UITableViewCell
  269. {
  270. let cell = tableView.dequeueReusableCell(
  271. withIdentifier: cellIdentifier, for: indexPath)
  272. guard
  273. indexPath.section < configurationSections.count
  274. && indexPath.row < configurationSections[indexPath.section].samples.count
  275. else { return cell }
  276. let sample = configurationSections[indexPath.section].samples[indexPath.row]
  277. cell.textLabel?.text = sample.name
  278. let switchView = UISwitch(frame: .zero)
  279. switchView.tag = Int(sample.tag)
  280. switch indexPath.section {
  281. case 0:
  282. if sample.tag - filterTagBase == configuration.autocompleteFilter.type.rawValue {
  283. switchView.setOn(true, animated: false)
  284. }
  285. case 1:
  286. let isOn = (sample.tag == configuration.location?.rawValue)
  287. switchView.setOn(isOn, animated: false)
  288. case 2:
  289. if configuration.placeFields == .all {
  290. switchView.setOn(true, animated: false)
  291. } else {
  292. let field = Int(configuration.placeFields.rawValue)
  293. if (field & switchView.tag) != 0 {
  294. switchView.setOn(true, animated: false)
  295. }
  296. }
  297. default:
  298. break
  299. }
  300. switchView.addTarget(self, action: sample.action, for: .valueChanged)
  301. cell.accessoryView = switchView
  302. return cell
  303. }
  304. func numberOfSections(in tableView: UITableView) -> Int {
  305. configurationSections.count
  306. }
  307. func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  308. guard section <= configurationSections.count else {
  309. return ""
  310. }
  311. return configurationSections[section].name
  312. }
  313. }
  314. extension ConfigurationViewController: UITableViewDelegate {
  315. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  316. let sample = configurationSections[indexPath.section].samples[indexPath.row]
  317. let cell = tableView.cellForRow(at: indexPath)
  318. guard let switchView = cell?.accessoryView as? UISwitch else { return }
  319. switchView.setOn(!switchView.isOn, animated: true)
  320. perform(sample.action, with: switchView)
  321. }
  322. }