きっかけ
現状にマンホールカードの配布場所へ経路案内を直接行うアプリケーションは無い!
配布場所へ行くにはGKP公式のホームページから住所をコピーするのが主流。
それでは工程が増えてしまい、非効率である。
なので、配布場所が一目で分かりつつ経路案内も行うマップAppを作成する。
マンホールカードとは
下水道広報プラットフォーム(GKP)が各地方自治体と協力して制作しているマンホールのコレクションカードである。1151種類のカードが制作されており、737自治体・3団体が協力している。(2025年8月時点)
開発
では実際に作ってみる!!!
・・・とは言ってみたもののswiftは勉強したこともない人間のため生成AIを利用しながら進めていきたいと思います。
- 実験環境
- MacBook Air 13in
- M4 チップ
- 対応バージョン
- MacOS Sequoia version15.4
- iOS 18.4
- XcodeVersion 16.3
簡単な地図アプリ作成
この章は、「SwiftUI対応 たった2日でマスターできる iPhoneアプリ開発集中講座 Xcode16/iOS18/Swift6.0対応」を参考にしています。全てとは言いませんがほとんどはこちらから引用しています。
- Swfitでマップを表示するにはMapKitをインポートしなければならない。
import MapKit
2. マップを表示
import MapKit
struct MapView: View {
var body: some View{
// Text("Hello, World!") 削除
Map() // マップを表示
}
}
実際に作成したアプリ
ContentView.swiftとMap.swiftの2つを作った。
基本構造
struct ContentView: View {
@StateObject private var viewModel = LocationViewModel()
@State private var position: MapCameraPosition = .automatic
@State private var showLocationList = false
var body: some View {
// ビューの内容
}
}
-
ContentViewはSwiftUIのViewプロトコルに準拠した構造体。アプリ画面を定義。 -
@StateObjectはデータを保持するviewModelオブジェクトを作成。画面が再構築されたとしてもデータが保持されるようになる。 -
@Stateは画面の状態を保持する変数を定義。値が変更されると自動的に画面が更新される。 -
positionは地図の表示位置や尺度を管理。 -
showLocationListは一覧表示画面の表示/非表示を切り替えるフラグ。
地図の表示
Map(position: $position) {
// マンホールカードの位置をマーカーで表示
ForEach(viewModel.locations) { location in
if viewModel.selectedLocation?.id == location.id {
Marker(location.title, coordinate: location.coordinate)
.tint(.red) // 選択中は赤色
} else {
Marker(location.title, coordinate: location.coordinate)
.tint(location.region.color)
}
}
// 選択された場所の詳細表示
// ...
// 経路の表示
// ...
// ユーザーの現在地表示
UserAnnotation()
}
struct Location: Identifiable {
let id = UUID()
let title: String
let coordinate: CLLocationCoordinate2D
let prefecture: String
let region: Region
}
-
Mapは上で紹介しているように地図を表示するのに必要な構造体です。 -
ForEachは配列の各要素に対して繰り返し処理を行う。配列にはマンホールカードの場所が入っており、場所毎にマーカーを配置。 -
Markerは地図上にピンを立てる構造体。施設名と座標を指定している。 -
tintはマーカーの色を設定する。選択中は赤色、それ以外は地域毎の色に分けられる。 -
UserAnnotationはユーザの現在地を地図上に表示させる。 -
Locationはマンホールカードの設置場所を表す構造体。上から順に、- id:各場所を識別するためのID
- title:場所の名前
- coordinate:緯度と経度の座標
- prefecture:都道府県
- region:地域区分
地図のコントロール
.mapControls {
MapUserLocationButton()
.padding(.trailing)
.buttonBorderShape(.circle)
MapCompass()
.padding(.trailing, 40)
MapScaleView()
}
-
.mapControlsは地図上のコントロール(ボタンなど)を配置。 -
MapUserLocationButtonは現在地に移動するボタンを表示。 -
MapCompassは方位を示すコンパスを表示。 -
MapScaleViewは地図の縮尺を示すスケールを表示。
経路情報の表示
if let route = viewModel.route {
VStack(alignment: .leading) {
Text("\(viewModel.selectedLocation?.title ?? "目的地") までの経路")
.font(.headline)
Text("距離: \(formatDistance(route.distance))")
Text("所要時間: \(formatTime(route.expectedTravelTime))")
// 交通手段の表示と切り替え
// ...
}
// ...
}
func calculateRoute() {
// 前回の検索をキャンセル
cancelAllDirectionsRequests()
// 位置情報と目的地の確認
guard let userLocation = userLocation, let selectedLocation = selectedLocation else {
// ...
return
}
// 経路計算を実行
// ...
}
選択された場所までの経路情報(距離・所要時間)を表示する。また、if文でrouteに値が存在する場合に縦方向に経路情報をビューに配置する。
また、車・徒歩・公共交通機関で経路を検索するようにします。各交通手段の所要時間を比較して最速の経路を選択する。
交通手段の切り替え
HStack {
Text("交通手段: \(viewModel.transportTypeName(viewModel.selectedTransportType))")
Spacer()
// 他の交通手段がある場合、切り替えボタンを表示
if viewModel.availableRoutes.count > 1 {
Menu {
// 交通手段のメニュー項目
// ...
} label: {
Image(systemName: "arrow.triangle.swap")
.foregroundColor(.blue)
}
}
}
現在選択されている交通手段を表示し、他の交通手段に切り替えるためのボタンを配置。Menuがドロップダウンメニューを扱う際の構造体。
マンホールカードの一覧表示
struct LocationListView: View {
@ObservedObject var viewModel: LocationViewModel
@Binding var isPresented: Bool
@State private var searchText = ""
var filteredLocations: [Location] {
if searchText.isEmpty {
return viewModel.locations
} else {
return viewModel.locations.filter { $0.title.contains(searchText) }
}
}
var body: some View {
NavigationView {
List(filteredLocations) { location in
// 各マンホールカードの情報を表示するボタン
// ...
}
.navigationTitle("マンホールカード一覧")
.searchable(text: $searchText, prompt: "検索")
// ...
}
}
}
-
LocationListViewはマンホールカード一覧を表示す画面の構造体。 -
@ObservedObjectはLocationViewModelから渡されたviewModelの変更を確認する。 - filteredLocations:検索テキストに基づいてフィルタリングされた場所の配列を計算する。
-
NavigationViewはナビゲーションバーを含むビューを作成。 -
Listスクロール可能なリストを作成。 -
.searchableは検索機能を追加する。
完成系
アプリアイコン(生成AI産)
アプリ画面
日本全国を表示した場合の画面.ピンの色を地域ごとのカードの色に合わせることで見やすくなった(と思う!)

一覧ボタンを作ることで目的の配布場所を調べやすくした。

実際に経路検索してみた!
遠くの場合は、目的地以外のピンは非表示にした。(しれっと車での経路検索も可能!?)

参考文献
この記事は以下の情報を参考にして執筆しました。

