You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
13 KiB
354 lines
13 KiB
// Copyright 2021 Google LLC. All rights reserved. |
|
// |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this |
|
// file except in compliance with the License. You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software distributed under |
|
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF |
|
// ANY KIND, either express or implied. See the License for the specific language governing |
|
// permissions and limitations under the License. |
|
|
|
import GooglePlaces |
|
import UIKit |
|
|
|
/// The section of configuration. |
|
struct ConfigSection { |
|
let name: String |
|
let samples: [ConfigData] |
|
} |
|
|
|
/// The configuration data. |
|
struct ConfigData { |
|
let name: String |
|
let tag: Int |
|
let action: Selector |
|
} |
|
|
|
/// The location option for autocomplete search. |
|
enum LocationOption: Int { |
|
case unspecified = 100 |
|
case canada = 101 |
|
case kansas = 102 |
|
|
|
var northEast: CLLocationCoordinate2D { |
|
switch self { |
|
case .canada: |
|
return CLLocationCoordinate2D(latitude: 70.0, longitude: -60.0) |
|
case .kansas: |
|
return CLLocationCoordinate2D(latitude: 39.0, longitude: -95.0) |
|
default: |
|
return CLLocationCoordinate2D() |
|
} |
|
} |
|
|
|
var southWest: CLLocationCoordinate2D { |
|
switch self { |
|
case .canada: |
|
return CLLocationCoordinate2D(latitude: 50.0, longitude: -140.0) |
|
case .kansas: |
|
return CLLocationCoordinate2D(latitude: 37.5, longitude: -100.0) |
|
default: |
|
return CLLocationCoordinate2D() |
|
} |
|
} |
|
} |
|
|
|
/// Manages the configuration options view for the demo app. |
|
class ConfigurationViewController: UIViewController { |
|
// MARK: - Properties |
|
|
|
private let cellIdentifier = "cellIdentifier" |
|
private let filterTagBase = 1000 |
|
|
|
private lazy var configurationSections: [ConfigSection] = { |
|
var sections: [ConfigSection] = [] |
|
|
|
let autocompleteFiltersSelector = #selector(autocompleteFiltersSwitch) |
|
let geocode = ConfigData( |
|
name: "Geocode", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.geocode.rawValue, |
|
action: autocompleteFiltersSelector) |
|
let address = ConfigData( |
|
name: "Address", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.address.rawValue, |
|
action: autocompleteFiltersSelector) |
|
let establishment = ConfigData( |
|
name: "Establishment", |
|
tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.establishment.rawValue, |
|
action: autocompleteFiltersSelector) |
|
let region = ConfigData( |
|
name: "Region", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.region.rawValue, |
|
action: autocompleteFiltersSelector) |
|
let city = ConfigData( |
|
name: "City", tag: filterTagBase + GMSPlacesAutocompleteTypeFilter.city.rawValue, |
|
action: autocompleteFiltersSelector) |
|
|
|
sections.append( |
|
ConfigSection( |
|
name: "Autocomplete filters", samples: [geocode, address, establishment, region, city])) |
|
|
|
let canada = ConfigData( |
|
name: "Canada", tag: LocationOption.canada.rawValue, action: #selector(canadaSwitch)) |
|
let kansas = ConfigData( |
|
name: "Kansas", tag: LocationOption.kansas.rawValue, action: #selector(kansasSwitch)) |
|
sections.append( |
|
ConfigSection(name: "Autocomplete Restriction Bounds", samples: [canada, kansas])) |
|
|
|
let placesFieldsSelector = #selector(placesFieldsSwitch) |
|
let name = ConfigData( |
|
name: "Name", tag: Int(GMSPlaceField.name.rawValue), action: placesFieldsSelector) |
|
let placeId = ConfigData( |
|
name: "Place ID", tag: Int(GMSPlaceField.placeID.rawValue), |
|
action: placesFieldsSelector) |
|
let plusCode = ConfigData( |
|
name: "Plus Code", tag: Int(GMSPlaceField.plusCode.rawValue), |
|
action: placesFieldsSelector) |
|
let coordinate = ConfigData( |
|
name: "Coordinate", tag: Int(GMSPlaceField.coordinate.rawValue), |
|
action: placesFieldsSelector) |
|
let openingHours = ConfigData( |
|
name: "Opening Hours", tag: Int(GMSPlaceField.openingHours.rawValue), |
|
action: placesFieldsSelector) |
|
let phoneNumber = ConfigData( |
|
name: "Phone Number", tag: Int(GMSPlaceField.phoneNumber.rawValue), |
|
action: placesFieldsSelector) |
|
let formattedAddress = ConfigData( |
|
name: "Formatted Address", tag: Int(GMSPlaceField.formattedAddress.rawValue), |
|
action: placesFieldsSelector) |
|
let rating = ConfigData( |
|
name: "Rating", tag: Int(GMSPlaceField.rating.rawValue), action: placesFieldsSelector) |
|
let ratingsTotal = ConfigData( |
|
name: "User Ratings Total", tag: Int(GMSPlaceField.userRatingsTotal.rawValue), |
|
action: placesFieldsSelector) |
|
let priceLevel = ConfigData( |
|
name: "Price Level", tag: Int(GMSPlaceField.priceLevel.rawValue), |
|
action: placesFieldsSelector) |
|
let types = ConfigData( |
|
name: "Types", tag: Int(GMSPlaceField.types.rawValue), action: placesFieldsSelector) |
|
let website = ConfigData( |
|
name: "Website", tag: Int(GMSPlaceField.website.rawValue), |
|
action: placesFieldsSelector) |
|
let viewPort = ConfigData( |
|
name: "Viewport", tag: Int(GMSPlaceField.viewport.rawValue), |
|
action: placesFieldsSelector) |
|
let addressComponents = ConfigData( |
|
name: "Address Components", tag: Int(GMSPlaceField.addressComponents.rawValue), |
|
action: placesFieldsSelector) |
|
let photos = ConfigData( |
|
name: "Photos", tag: Int(GMSPlaceField.photos.rawValue), action: placesFieldsSelector) |
|
let minutes = ConfigData( |
|
name: "UTC Offset Minutes", tag: Int(GMSPlaceField.utcOffsetMinutes.rawValue), |
|
action: placesFieldsSelector) |
|
let status = ConfigData( |
|
name: "Business Status", tag: Int(GMSPlaceField.businessStatus.rawValue), |
|
action: placesFieldsSelector) |
|
let iconImageURL = ConfigData( |
|
name: "Icon Image URL", tag: Int(GMSPlaceField.iconImageURL.rawValue), |
|
action: placesFieldsSelector) |
|
let iconBackgroundColor = ConfigData( |
|
name: "Icon Background Color", tag: Int(GMSPlaceField.iconBackgroundColor.rawValue), |
|
action: placesFieldsSelector) |
|
sections.append( |
|
ConfigSection( |
|
name: "Place Fields", |
|
samples: [ |
|
name, placeId, plusCode, coordinate, openingHours, phoneNumber, formattedAddress, rating, |
|
ratingsTotal, priceLevel, types, website, viewPort, addressComponents, photos, minutes, |
|
status, iconImageURL, iconBackgroundColor, |
|
])) |
|
return sections |
|
}() |
|
|
|
private var configuration: AutocompleteConfiguration |
|
|
|
private lazy var tableView: UITableView = { |
|
let tableView = UITableView() |
|
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) |
|
tableView.dataSource = self |
|
tableView.delegate = self |
|
tableView.translatesAutoresizingMaskIntoConstraints = false |
|
return tableView |
|
}() |
|
|
|
private lazy var closeButton: UIButton = { |
|
let button = UIButton() |
|
button.backgroundColor = .blue |
|
button.setTitle("Close", for: .normal) |
|
button.setTitleColor(.white, for: .normal) |
|
button.translatesAutoresizingMaskIntoConstraints = false |
|
button.addTarget(self, action: #selector(tapCloseButton(_:)), for: .touchUpInside) |
|
return button |
|
}() |
|
|
|
// MARK: - Public functions |
|
|
|
public init(configuration: AutocompleteConfiguration) { |
|
self.configuration = configuration |
|
super.init(nibName: nil, bundle: nil) |
|
} |
|
|
|
required init(coder: NSCoder) { fatalError() } |
|
|
|
override func viewDidLoad() { |
|
super.viewDidLoad() |
|
|
|
view.addSubview(tableView) |
|
view.addSubview(closeButton) |
|
let guide = view.safeAreaLayoutGuide |
|
NSLayoutConstraint.activate([ |
|
tableView.topAnchor.constraint(equalTo: guide.topAnchor), |
|
tableView.bottomAnchor.constraint(equalTo: closeButton.topAnchor), |
|
tableView.leadingAnchor.constraint(equalTo: guide.leadingAnchor), |
|
tableView.trailingAnchor.constraint(equalTo: guide.trailingAnchor), |
|
]) |
|
NSLayoutConstraint.activate([ |
|
closeButton.bottomAnchor.constraint(equalTo: guide.bottomAnchor), |
|
closeButton.leadingAnchor.constraint(equalTo: guide.leadingAnchor), |
|
closeButton.trailingAnchor.constraint(equalTo: guide.trailingAnchor), |
|
]) |
|
} |
|
|
|
// MARK: - Private functions |
|
|
|
@objc private func tapCloseButton(_ sender: UISwitch) { |
|
dismiss(animated: true) |
|
guard let location = configuration.location else { return } |
|
let northEast = location.northEast |
|
let southWest = location.southWest |
|
// Update configuration |
|
switch location { |
|
case .canada: |
|
configuration.autocompleteFilter.origin = CLLocation( |
|
latitude: northEast.latitude, longitude: northEast.longitude) |
|
configuration.autocompleteFilter.locationRestriction = |
|
GMSPlaceRectangularLocationOption(northEast, southWest) |
|
case .kansas: |
|
configuration.autocompleteFilter.origin = CLLocation( |
|
latitude: northEast.latitude, longitude: northEast.longitude) |
|
configuration.autocompleteFilter.locationRestriction = |
|
GMSPlaceRectangularLocationOption(northEast, southWest) |
|
default: |
|
configuration.autocompleteFilter.origin = nil |
|
configuration.autocompleteFilter.locationRestriction = nil |
|
} |
|
} |
|
|
|
@objc private func autocompleteFiltersSwitch(_ sender: UISwitch) { |
|
for sample in configurationSections[0].samples { |
|
guard let switchView = view.viewWithTag(sample.tag) as? UISwitch else { continue } |
|
if switchView.tag != sender.tag { |
|
switchView.setOn(false, animated: true) |
|
} |
|
} |
|
// The value of the type is tag - filterTagBase |
|
guard let type = GMSPlacesAutocompleteTypeFilter(rawValue: sender.tag - filterTagBase) else { |
|
return |
|
} |
|
configuration.autocompleteFilter.type = type |
|
} |
|
|
|
@objc private func canadaSwitch(_ sender: UISwitch) { |
|
if sender.isOn { |
|
// Turn off the Kansas switch |
|
guard let switchView = view.viewWithTag(LocationOption.kansas.rawValue) as? UISwitch else { |
|
return |
|
} |
|
switchView.setOn(false, animated: true) |
|
configuration.location = .canada |
|
} else { |
|
configuration.location = .unspecified |
|
} |
|
} |
|
|
|
@objc private func kansasSwitch(_ sender: UISwitch) { |
|
if sender.isOn { |
|
// Turn off the Canada switch |
|
guard let switchView = view.viewWithTag(LocationOption.canada.rawValue) as? UISwitch else { |
|
return |
|
} |
|
switchView.setOn(false, animated: true) |
|
configuration.location = .kansas |
|
} else { |
|
configuration.location = .unspecified |
|
} |
|
} |
|
|
|
@objc private func placesFieldsSwitch(_ sender: UISwitch) { |
|
var field = UInt(sender.tag) |
|
if sender.isOn { |
|
field |= configuration.placeFields.rawValue |
|
} else { |
|
field = ~field & configuration.placeFields.rawValue |
|
} |
|
configuration.placeFields = GMSPlaceField(rawValue: field) |
|
} |
|
} |
|
|
|
extension ConfigurationViewController: UITableViewDataSource { |
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
|
guard section <= configurationSections.count else { |
|
return 0 |
|
} |
|
return configurationSections[section].samples.count |
|
} |
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) |
|
-> UITableViewCell |
|
{ |
|
let cell = tableView.dequeueReusableCell( |
|
withIdentifier: cellIdentifier, for: indexPath) |
|
guard |
|
indexPath.section < configurationSections.count |
|
&& indexPath.row < configurationSections[indexPath.section].samples.count |
|
else { return cell } |
|
let sample = configurationSections[indexPath.section].samples[indexPath.row] |
|
cell.textLabel?.text = sample.name |
|
let switchView = UISwitch(frame: .zero) |
|
switchView.tag = Int(sample.tag) |
|
switch indexPath.section { |
|
case 0: |
|
if sample.tag - filterTagBase == configuration.autocompleteFilter.type.rawValue { |
|
switchView.setOn(true, animated: false) |
|
} |
|
case 1: |
|
let isOn = (sample.tag == configuration.location?.rawValue) |
|
switchView.setOn(isOn, animated: false) |
|
case 2: |
|
if configuration.placeFields == .all { |
|
switchView.setOn(true, animated: false) |
|
} else { |
|
let field = Int(configuration.placeFields.rawValue) |
|
if (field & switchView.tag) != 0 { |
|
switchView.setOn(true, animated: false) |
|
} |
|
} |
|
default: |
|
break |
|
} |
|
switchView.addTarget(self, action: sample.action, for: .valueChanged) |
|
cell.accessoryView = switchView |
|
return cell |
|
} |
|
|
|
func numberOfSections(in tableView: UITableView) -> Int { |
|
configurationSections.count |
|
} |
|
|
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { |
|
guard section <= configurationSections.count else { |
|
return "" |
|
} |
|
return configurationSections[section].name |
|
} |
|
} |
|
|
|
extension ConfigurationViewController: UITableViewDelegate { |
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
|
let sample = configurationSections[indexPath.section].samples[indexPath.row] |
|
let cell = tableView.cellForRow(at: indexPath) |
|
guard let switchView = cell?.accessoryView as? UISwitch else { return } |
|
switchView.setOn(!switchView.isOn, animated: true) |
|
perform(sample.action, with: switchView) |
|
} |
|
}
|
|
|