ゴール
以前、画面に地図を表示し、ピンを決め打ちで指すことができるようになった。このピンを、UIから取得した任意の場所に刺せるようにしたい。
調査
- 普通にstoryboardでボタン、ラベルを配置すると、地図がそれらより全面に描画される?ため、検索欄などを儲けることができない。
- 地点検索の実装に関して、公式にめちゃくちゃご親切に書いてあるけど、実行して見ると動かない部分もあり、理解が追いつかないので一つずつ進める。
- 地図より前面にボタンなどを配置しようと思うと、storyboardではなく、ViewController.swiftのPG上で実装する必要がありそう。実行の順番の問題?
-
delegateについて気になったので調査。ほぼinterefaceじゃんと思いましたが、厳密には違うみたい。全然違った。delegate - 地名や建物名から検索を行う機能はいくつかあるみたいだが、この3つ(ジオコーディング、オートコンプリートサービス、プレイス検索)の中だと、曖昧検索の結果の中から1つを選択することのできる、「オートコンプリートサービス」が良い。
実装
ボタンを配置する。
func makeButton() {
let btnLaunchAc = UIButton(frame: CGRect(x: 25, y: 25, width: 80, height: 80))
btnLaunchAc.backgroundColor = .clear
btnLaunchAc.setTitle("Pos", for: .normal)
btnLaunchAc.setTitleColor(.blue, for: .normal)
btnLaunchAc.addTarget(self, action: #selector(getMyPosition), for: .touchUpInside)
self.view.addSubview(btnLaunchAc)
}
ラベル×2を配置する。
func makeLabel() {
nameLabel = UILabel(frame: CGRect(x:105, y:25, width:250, height:40))
addressLabel = UILabel(frame: CGRect(x:105, y:65, width:250, height:40))
nameLabel.textColor = .blue
addressLabel.textColor = .blue
//nameLabel.backgroundColor = .white
//addressLabel.backgroundColor = .white
nameLabel.text = "name"
addressLabel.text = "address"
self.view.addSubview(nameLabel)
self.view.addSubview(addressLabel)
}
テキストフィールドを配置
func makeTextField() {
textField = UITextField()
textField.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.size.width-20, height: 38)
textField.placeholder = "目的地"
textField.keyboardType = .default
textField.borderStyle = .roundedRect
textField.returnKeyType = .done
textField.clearButtonMode = .always
self.view.addSubview(textField)
}
完了ボタンを押すとキーボードが閉じるように以下3点を追加(参考)
//1
class ViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//2
textField.delegate = self
//3
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
こちらを参考に、テキストフィールドの値が変わるたびに呼ばれる関数を定義(オートコンプリートを呼びたい)
// textFieldに対して追加
textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)
// 関数を追加
@objc func textFieldDidChange(_ textFiled: UITextField) {
}
→オートコンプリートを勘違いしてた。文字が変更されるたびに呼ぶ必要はなく、入力が開始されるタイミングで一度呼べば良いので、実装を以下に変更。(参考)
// textFieldに対して追加
textField.addTarget(self, action: #selector(self.textFieldChange(_:)), for: UIControl.Event.editingDidBegin)
// 関数を追加
@objc func textFieldChange(_ textFiled: UITextField) {
}
テキストフィールドが呼ばれたタイミングで、別画面が表示されるよう、textFieldChange
関数内に以下を追加する。
@objc func textFieldChange(_ textFiled: UITextField) {
// ViewController?を追加
let autocompleteController = GMSAutocompleteViewController()
// delegateを指定する
autocompleteController.delegate = self
// autocompleteControllerを表示する
present(autocompleteController, animated: true, completion: nil)
}
autocompleteController
で行われる処理を実装する。
extension ViewController: GMSAutocompleteViewControllerDelegate {
// ①
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
textField.text = place.name
dismiss(animated: true, completion: nil)
}
// ②
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO
}
// ③
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
}
GMSAutocompleteViewControllerDelegate
に飛んで、各メソッドの役割を確認してみる。
①
利用可能なオートコンプリート予測から場所が選択されたときに呼び出されます。
このメソッドの実装では、View Controller が終了しないため、View Controller を終了する必要があります。
自分自身をdismissしてください。
②
オートコンプリートの予測または場所を取得するときに再試行不可能なエラーが発生したときに呼び出されます
詳細。 再試行不可能なエラーは、すぐに修正される可能性が低いエラーとして定義されます
操作を再試行してください。
③
ユーザーが |GMSAutocompleteViewController| の [キャンセル] ボタンをタップしたときに呼び出されます。
このメソッドの実装では、View Controller が終了しないため、View Controller を終了する必要があります。
自分自身をdismissしてください。
エラー処理は一旦置いておいて、予測された場所の一覧の中から一つを選んだときに、テキストフィールドに値が入るように実装した。
あとは、選択した地名やアドレスから、緯度軽度を取得して、ピンを指す値として渡してあげる。
マーカーと、カメラの位置を指定する部分を関数化して、初めに呼ばれるとき以外は、指定したカメラのポジションに移動するように実装
func PointPlace(pos: CLLocationCoordinate2D, title: String?) {
camera = GMSCameraPosition.camera(withTarget: pos, zoom: 10.0)
if(first){
mapView = GMSMapView.map(withFrame: self.view.frame, camera: camera)
self.view.addSubview(mapView)
}else{
self.mapView.animate(to: camera)
}
marker.position = pos
marker.title = title
marker.map = mapView
}
上記①の中で、位置と名前を渡してあげる。
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
textField.text = place.name
// 位置と名前を渡す
PointPlace(pos: place.coordinate, title: place.name)
dismiss(animated: true, completion: nil)
}
できた!