質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
iOS 9

iOS 9は、アップル社のモバイルOSであるiOSシリーズのバージョン。特徴として検索機能の強化、Siriの機能改良、iPad向けマルチタスクなどがあります。マルチウィンドウ機能をサポートし、iPad向けマルチタスクもサポートされています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1900閲覧

swiftUI MAP上のPinをButtonで消すには

masa328

総合スコア51

iOS 9

iOS 9は、アップル社のモバイルOSであるiOSシリーズのバージョン。特徴として検索機能の強化、Siriの機能改良、iPad向けマルチタスクなどがあります。マルチウィンドウ機能をサポートし、iPad向けマルチタスクもサポートされています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2022/02/03 13:16

CLLocationManager()を使って位置情報を取得し、マップ上にPinを表示した後、(たとえば)消すボタンでピンを消したいです。
clear_flagのture/falseでstruct MapView内のfunc updateUIView内で消すように考えたのですが、そもそも消すボタンを押してもlocationが更新されないから(?)うまくfunc updateUIViewに飛びません。

そもそも、このような面倒なことをしなくても、なんらかのfunc等を呼べば、マップ内のPin等追加した要素が消せる方法等もあるのでしょうか?

すいません。お知恵を拝借したく、よろしくお願いいたします。

ContentView.swift

1// ContentView.swift 2import SwiftUI 3import MapKit 4var latetude : Double = 0.0 5var longitude : Double = 0.0 6var clear_flag = false 7 8struct ContentView: View { 9 10 var body: some View { 11 12 let latetude = $manager.location.wrappedValue.coordinate.latitude 13 let longitude = $manager.location.wrappedValue.coordinate.longitude 14 15 Button(action: { //delete 16 clear_flag = true 17 }) 18 { 19 Image(systemName: "delete") 20 } 21 ZStack(alignment: .bottomTrailing){ 22 MapView(mapType: .standard,latetude: latetude,longitude: longitude) 23 { 24 Image(systemName: "map") 25 .resizable() 26 .frame(width: 35.0,height: 35.0, alignment: .leading) 27 } 28 } 29 } 30}

MapView.swift

1// MapView.swift 2 3import SwiftUI 4import MapKit 5 6var runPoint : [[Double]] = [[ ]] 7 8struct MapView: UIViewRepresentable{ 9 let latetude: Double 10 let longitude: Double 11 12 typealias UIViewType = MKMapView 13 14 func makeCoordinator() -> MapViewCoordinator { 15 return MapViewCoordinator() 16 } 17 18 func makeUIView(context: Context) -> MKMapView { 19 MKMapView() 20 } 21 22 func updateUIView(_ uiView: MKMapView, context: Context) { 23 uiView.delegate = context.coordinator 24 let pin = MKPointAnnotation() 25 26 if clear_flag == true{ 27 pin.coordinate.longitude = longitude 28 pin.coordinate.latitude = latetude 29 uiView.mapView.removeAnnotation(annotation) 30 }else{ 31 pin.coordinate.longitude = longitude 32 pin.coordinate.latitude = latetude 33 uiView.addAnnotation(pin) 34 } 35 36 uiView.region = MKCoordinateRegion( 37 center: pin.coordinate, 38 latitudinalMeters: 500.0, 39 longitudinalMeters:500.0) 40 } 41}

LocationManager.swift

1// LocationManager.swift 2 3import SwiftUI 4import MapKit 5 6class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { 7 let manager = CLLocationManager() 8 @Published var location = CLLocation() 9 10 override init() { 11 super.init() 12 self.manager.delegate = self 13 self.manager.requestWhenInUseAuthorization() 14 self.manager.desiredAccuracy = kCLLocationAccuracyBest 15 self.manager.distanceFilter = 10 16 self.manager.allowsBackgroundLocationUpdates = true 17 self.manager.startUpdatingLocation() 18 } 19 20 func locationManager(_ manager: CLLocationManager, 21 didUpdateLocations locations: [CLLocation]) { 22 if map_start == true{ 23 self.location = locations.last! 24 } 25 } 26} 27

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2022/02/03 17:47

「なんらかのfunc等を呼べば、要素が消せる」というのは、UIKit などの伝統的・手続き的な UI の考え方ですね。 SwiftUI は宣言的 UI なので、ピンを消したいということは、まず「ピンを表示している状態」「ピンが表示されてない状態」を @State などで区別できるようにして、それを画面に反映させる、という考え方をする必要があります。
guest

回答1

0

ベストアンサー

まず、削除したいアノテーション(Pin)をどう取得するかが問題です。

アノテーションをタップして取得する方法もありますが、ここでは追加したものをそのまま削除できれば良いので、アノテーションを追加したタイミングで同時にそのアノテーションの参照を取得できれば最終的にremoveAnnotationで解決できそうです。

では、アノテーションを追加したタイミングでどう取得するか?

MapViewには便利なデリゲートパターンがデフォルトで用意されているのでそれを使います。

NSObjectとMKMapViewDelegateを継承したクラスMapViewCoordinatorをMapView構造体の中に作成する。

swift

1// MapView.swift 2struct MapView: UIViewRepresentable { 3 4 (省略) 5 6 class MapViewCoordinator: NSObject, MKMapViewDelegate { 7 var parent: MapView 8 9 init(_ parent: MapView){ 10 self.parent = parent 11 super.init() 12 } 13 } 14}

次に、アノテーションを追加した時に自動で呼び出されるデリゲートメソッドmapView(_:viewFor:)を定義する。

この中でアノテーションのView(UIAnnotationView)を取得できるので、BindingなどでMapViewの上位のビューであるContentViewと共有しましょう。

swift

1// MapView.swift 2struct MapView: UIViewRepresentable { 3 @Binding var map: MKMapView 4 @Binding var sharedAnnotationView: MKAnnotationView 5 6 (省略) 7 8 class MapViewCoordinator: NSObject, MKMapViewDelegate { 9 10 (省略) 11 12 // アノテーション追加時に呼ばれるメソッド 13 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 14 // MKMarkerAnnotationViewクラスはMKAnnotationViewクラスを継承したクラス。 15 // mapView(_:viewFor:)メソッド内で宣言することでピンの見た目を色々カスタマイズできる。 16 let annotationView = MKMarkerAnnotationView() 17 // ここでMKAnnotationViewをContentViewと共有する 18 self.parent.sharedAnnotationView = annotationView 19 20 return annotationView 21 } 22 } 23}

あとはContentView側でMKMapView.removeAnnotation()を呼び出せばMapView外の操作でピンを削除することができます。

最終的にコード全体は下記のような感じになります。

(現在地情報の取得を考えると複雑になるので今回は触れていません)

swift

1// ContentView.swift 2 3import SwiftUI 4import MapKit 5 6struct ContentView: View { 7 @State var map = MKMapView() 8 @State var sharedAnnotationView = MKAnnotationView() 9 10 var body: some View { 11 VStack{ 12 MapView(map: $map, sharedAnnotationView: $sharedAnnotationView) 13 14 // 削除ボタン 15 Button(action: { 16 map.removeAnnotation(sharedAnnotationView.annotation!) 17 }, label: { 18 Text("Remove annotation") 19 }).frame(height: 100) 20 21 // 追加ボタン 22 Button(action: { 23 let annotation = MKPointAnnotation() 24 // アノテーションを追加する座標を指定 25 annotation.coordinate = CLLocationCoordinate2D(latitude: 35.3, 26 longitude: 139.4) 27 // アノテーションのキャプションを指定 28 annotation.title = "sample" 29 map.addAnnotation(annotation) 30 }, label: { 31 Text("Add annotation") 32 }).frame(height: 100) 33 } 34 } 35}

swift

1// MapView.swift 2import SwiftUI 3import MapKit 4 5struct MapView: UIViewRepresentable { 6 @Binding var map: MKMapView 7 @Binding var sharedAnnotationView: MKAnnotationView 8 9 func makeUIView(context:Context) -> MKMapView { 10 map.delegate = context.coordinator 11 return map 12 } 13 14 func makeCoordinator() -> MapViewCoordinator { 15 MapViewCoordinator(self) 16 } 17 18 func updateUIView(_ uiView: MKMapView, context: Context) { 19 } 20 21 class MapViewCoordinator: NSObject, MKMapViewDelegate { 22 var parent: MapView 23 24 init(_ parent: MapView){ 25 self.parent = parent 26 super.init() 27 } 28 29 // アノテーション追加時に呼ばれるメソッド 30 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 31 // MKMarkerAnnotationViewクラスはMKAnnotationViewクラスを継承したクラス。 32 // mapView(_:viewFor:)メソッド内で宣言することでピンの見た目を色々カスタマイズできる。 33 let annotationView = MKMarkerAnnotationView() 34 35 // ここでMKAnnotationViewをContentViewと共有する 36 self.parent.sharedAnnotationView = annotationView 37 38 return annotationView 39 } 40 } 41}

イメージ説明

ご参考になれば幸いです。

投稿2022/02/05 04:26

chitomo

総合スコア68

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masa328

2022/02/11 09:50

詳しい詳細な説明いただき誠にありがとうございます。 返事が個人的な都合により遅くなりまして大変申し訳ございませんでした。 いただいたコード例を試させていただきました。この例ですとpinが一つですが、複数になった場合どうしたらよろしいでしょうか。 試してみたのはいただいたコードの func updateUIView に let annotation = MKPointAnnotation() annotation.coordinate = CLLocationCoordinate2D(latitude: 38.3, longitude: 139.4) map.addAnnotation(annotation) とか行こなったあと、ContentView内のbuttonでもう一つのpinを立てた後、removeボタンを押しても一つしか消えない等です。 要領得ない説明で申し訳ありませんがよろしくお願いします。
chitomo

2022/02/11 17:06

複数同時に消すのであればMapKitのremoveAnnotations(_:)メソッドにMKAnnotation型の配列を引数として渡してあげればまとめて消すことができます。 https://developer.apple.com/documentation/mapkit/mkmapview/1452130-removeannotations この場合、BindingするsharedAnnotationViewもMKAnnotation型の配列([MKAnnotation])にしてあげて、 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {...} 内の self.parent.sharedAnnotationView = annotationView は self.parent.sharedAnnotationView.append(annotationView) のようにすれば良いかと思います。
masa328

2022/02/12 00:14

早々にありがとうございます。 MapView内の @Binding var sharedAnnotationView: [MKAnnotationView] self.parent.sharedAnnotationView.append(annotationView) に変更 ContentView内で @State var sharedAnnotationView = [MKAnnotationView()] にしましたが map.removeAnnotations([sharedAnnotationView.annotation!]) のところでエラーが出てしまいます。(Annotationはsをつけました) どう考えばいいでしょうか。 それと、追加で申し訳ないのですが、pinではなくpolylineの場合は全体的にどう考えればいいのでしょうか?
chitomo

2022/02/12 07:42

.removeAnnotationsの引数にはMKAnnotation型のArrayが指定されているので、Bindingするプロパティの型は[MKAnnotationView]ではなく[MKAnnotation]にした方がやりやすいと思います。 なのでMapViewで宣言すべきは @Binding var sharedAnnotationView: [MKAnnotation] となります。 各ピンのMKAnnotationはfunc mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation)の中で取得することができるので、 self.parent.sharedAnnotationView.append(annotation) としてあげて、最後にContentViewで map.removeAnnotations(sharedAnnotationArray) を入れてあげると複数のピンをまとめて削除することができるはずです。 あと、polylineの場合はaddOverlayメソッドを使って描写するやり方を使ってますかね? その場合も考え方としては同じで、removeOverlayメソッドに同じくpolylineを渡してあげれば済む話だと思います。 https://developer.apple.com/documentation/mapkit/mkmapview/1451921-removeoverlay
masa328

2022/02/12 11:30

お付き合いいただきありがとうございます。 map.removeAnnotations(sharedAnnotationArray) ですが、「Cannot find 'sharedAnnotationArray' in scope」エラーが出てしまいます。 ContentView.swiftには @State var sharedAnnotationView = [MKAnnotationView()] MapView(map: $map, sharedAnnotation: sharedAnnotationView) 等を書き換えたのですが、何か間違っていますでしょうか
chitomo

2022/02/13 02:35

失礼、間違えました。 map.removeAnnotations(sharedAnnotationArray) ではなく map.removeAnnotations(sharedAnnotationView) です。
masa328

2022/02/13 03:20

すいません。一度そう思って map.removeAnnotations(sharedAnnotationView) にしてみたのですが、そうすると MapView(map: $map, sharedAnnotation: sharedAnnotationView) が 「Cannot convert value of type '[MKAnnotationView]' to expected argument type 'Binding<[MKAnnotation]>'」エラー map.removeAnnotations(sharedAnnotationView) が 「Cannot convert value of type '[MKAnnotationView]' to expected argument type '[MKAnnotation]'」エラーが出てしまいます。 MapView(map: $map, sharedAnnotation: sharedAnnotationArray) に書き換えると 「Cannot find 'sharedAnnotationArray' in scope」が出ますが、 map.removeAnnotations(sharedAnnotationView) のエラーは消えます。 他のところで勘違いしているのでしょうか
chitomo

2022/02/13 09:00

機械翻訳使ってでも良いので、エラー文を読めるようになることをお勧めします。 「Cannot convert value of type '[MKAnnotationView]' to expected argument type 'Binding<[MKAnnotation]>'」は「'[MKAnnotationView]' 型は 'Binding<[MKAnnotation]>' 型に変換できません」という意味です。 ここではMapViewの第二引数に 'Binding<[MKAnnotation]>' 型の変数を指定しなければならないため、ContentViewの変数宣言部で @State var sharedAnnotationView: [MKAnnotation] = [] と宣言し、MapViewを呼び出す箇所では MapView(map: $map, sharedAnnotation: $sharedAnnotationView) とバインディングしてあげれば良いはずです。 また、「map.removeAnnotations(sharedAnnotationView)」で「Cannot convert value of type '[MKAnnotationView]' to expected argument type '[MKAnnotation]'」のエラーが出るのはsharedAnnotationViewのプロパティ宣言時に[MKAnnotation]型ではなく[MKAnnotationView]型で宣言しているからなので、上記のように[MKAnnotation]型で宣言しなおせばこちらもエラーは出なくなるかと思います。
masa328

2022/02/13 11:00

chitomoさん 対応いただきまして誠にありがとうございました。 お陰様でchitomoさんのサンプルで複数PINの削除が可能になりました。 この後、自分のコードで試してみます。
chitomo

2022/02/14 12:07

吉報、ありがとうございます。 無事にmasa328さんのコードでも実装できることを祈願いたします!
masa328

2022/02/16 01:15

chitomoさん、すいません。もう一点教えていただけますか。申し訳ございません。 polylineを同様に操作しようと思って map.removeOverlays(sharedOverlayView) を使おうと思っています。 その中で class MapViewCoordinator: NSObject, MKMapViewDelegate {} 内の func mapView(_ mapView: MKMapView, viewFor overlay: MKOverlay) -> MKAnnotationView? { let overlayView = MKMarkerAnnotationView() self.parent.sharedOverlayView.append(overlay) return overlayView } に書き換えようとしているのですが、ここで MKAnnotationView や MKMarkerAnnotationView() を 何に書き換えたらいいかわかりません。 そもそも viewFor を MKOverlay に対して使えるのかもよくわからないでいます。 お手すきの時でいいのでアドバイスいただけますでしょうか。よろしくお願いいたします。
masa328

2022/02/26 12:50

poly lineに関しては改めて、質問させていだだきました。 本件の質問に関しては解決しています。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問