今回は、↓こちらの講座で作ったMapアプリの復習です
【iOS14対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp
Mapアプリ
主な仕様は2つあります。
①マップ上で特定の位置をロングタップすると、その位置の住所が表示される
②緯度経度を入力すると、その位置のマップと住所が表示される
但し、このアプリでは正確なバインディングは行ってないので雑に緯度経度を入力したり、何もない所を指定すると落ちます。
このアプリでは主にデリゲートとプロトコルの仕組みを理解することを目的としています。
UI
左の画面がViewController
クラス、右の画面がNextViewController
クラスです。
Map機能を実装する
Main.StoryBoard
ではMKMapView
という要素を使用します。
ViewController
ではマップ機能を実装するためにimport MapKit
、import Corelocation
が必要です。
クラスには、住所を取り扱うためのCLLocationManagerDelegate
、ロングタップを使えるようにするためのUIGestureRecognizerDelegate
を継承します。
すると冒頭は以下のようになります。
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, UIGestureRecognizerDelegate {
// 中略
}
ロングタップを実装する
Main.StoryBoard
上でLong Press Gesture Recognizer
というパーツをMKMapView
の上に置き、プログラムにIBoutlet
でlongPress
という名前でインスタンス化します。
さらにもう一度プログラムに繋ぎ、IBAction
で、Type
をUILongpressRecognizer
に変更してlongPressTap
という名前でインスタンス化します。
この時、IBAction
の引数sender
に、ロングタップに関する状態についての情報が入っているので、この引数を使用して中身の処理を記述します。
@IBAction func longPressTap(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began{
//タップを開始した時の処理
} else if sender.state == .ended{
//タップが終了した(=画面から手を離した)時の処理
}
//中略
}
今回は終わった時だけに処理を記述します。
この時の処理の流れとしては、
①タップした位置を指定してMKMapView上の緯度経度を取得する
②取得した緯度経度を住所に変換する
という流れになります。
ここで、この処理を記述する前に、緯度経度を住所に変換する関数を先に作成します。
func convert(lat:CLLocationDegrees, log:CLLocationDegrees){
//↑latは緯度を入れる引数、logは経度を入れる引数です
let geocoder = CLGeocoder()
let location = CLLocation(latitude:lat, lognitude:log)
// ↑引数に入れられた値がここに入りますね
//クロージャーという仕組みを使用しています↓
//クロージャー内で宣言していない変数に関しては全てselfを記述する必要があります
//クロージャーは値が入ってくるまでは発動しないようになっています
geocoder.reverseGeocodeLocation(location) { (placeMark, error) in
// ↓が空でなければ、という条件分岐
if let placeMark = placeMark{
//placeMarkには住所が入っています 値が入っているかで条件分岐をしています
if let pm = placeMatk.first{
if pm.administrativeArea != || pm.locality != nil {
//↓には〇〇県、〇〇市といった住所に関する情報が入っています
self.addressString = pm.name! + pm.anministArea! + pm.locality!
} else {
//上のような整った住所がない場合の処理
self.addressString = pm.name!
}
self.addresslabel.text = self.addressString
}
}
}
}
それではロングタップの実装に戻ります。
@IBAction func longPressTap(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began{
//タップを開始した時の処理
} else if sender.state == .ended{
//タップが終了した(=画面から手を離した)時の処理
//タップした位置から緯度経度を取得する
let tapPoint = sender.location(in: view) //viewの中でタップしたポイントを変数に格納
//タップした位置(CGPoint)を指定してMKViewMap上の緯度経度を取得する
//つまり、Mapview上でタップした部分をconvertメソッドを用いて緯度経度に変換している
let center = mapView.convert(tapPoint, toCoordinateFrom: mapView)
let lat = center.latitude
let log = center.latitude
//住所を取得する ここで、先ほど作成したメソッドを発動させます
convert(lat: lat, log: log)
//convertメソッドですでに住所はロングタップした部分に反映されています
}
入力した緯度経度からMapと住所を取得する
ViewController
の設定ボタンから緯度経度入力画面に遷移するよう実装します(遷移の実装は割愛)
そして、緯度経度入力画面からMapの画面に値を渡すためのプロトコルを設定します。
まず、値を受け取るViewController
に以下を記述します。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//忘れがちですがsegueのidentifierに"next"を入力します
if segue.identifier == "next" {
let nextVC = segue.destination as! NextViewController
nextVC.delegate = self
}
}
次にNextViewController
にプロトコルを記述していきます。
protocol SearchLocationDelegate {
func searchLocation(idoValue:String, keidoValue:String)
}
このメソッドを使用できるよう、class NextViewController
配下に以下の記述を行います。
var delegate = SearchLocationDelegate?
OKボタン
を押したら、入力された緯度と経度をViewController
に渡したいので、OKボタン
をプログラムにつないでokAction
と名前をつけインスタンス化したら、
@IBAction func okAction(_ sender: Any) {
//テキストフィールドに入力された値を取得し
let idoValue = idoTextField.text!
let keidoValue = keidoTextField.text!
//デリゲートメソッドの引数に入れてデリゲートメソッドを発動させる
//2つのテキストフィールドに値があれば画面遷移を行うという条件分岐を設ける
if idoTextField != nil && keidoTextField != nil {
delegate?.searchLocation(idoValue:String(idoValue), keidoValue:String(keidoValue))
}
ViewController
に委任するためにSearchLocationDelegate
をclass
に追記し、デリゲートメソッドを追加します。
func searchLocation(idoValue:String, keidoValue:String) {
//デリゲートメソッドで渡された値がちゃんとあるかどうかで条件分岐
if idoValue.isEmpty != true && keidoValue.isEmpty != true {
let idoString = idoValue
let keidoString = keidoValue
//緯度経度からコーディネート(=画面の中心に持ってくる)
let coordinate = CLLocationCoordinate2DMake(Double(idoString)!, Double(keidoString)!)
//Map上で表示する範囲を指定
let span = MKCoodinateSpan(latitudeData: 0.01,
lognitudeData: 0.01)
//Mapの領域を指定
let region = MKCoodinateRegion(coordinate, span: span)
//領域をmapViewに設定
mapView.setRegion(region, animated: true)
//受け取った緯度経度から住所に変換する(すでに作成済み)
convert(lat:Double(idoString)!, log:Double(keidoString)!)
} else {
//もし値を取得できなければ
addressLabel.text = "表示できません"
}
}
以上で完成です。
感想
言われてみればなんとなくわかるけど、説明しろと言われるとなかなか難しい、っていう状態ですね。
Appleが用意しているプロトコルをたくさん回数を重ねて使用すればわかってくるようになってくる気がします。
引き続き根気強く学習していきます。