Upgrade to Pro — share decks privately, control downloads, hide ads and more …

無料で使えて意外と凄いMapKitの世界

 無料で使えて意外と凄いMapKitの世界

iOSDC Japan 2023 -DAY1-(ルーキーズLT)
https://fortee.jp/iosdc-japan-2023/proposal/b65bd1c5-1a4b-4e84-bda3-1a2b86a11cfa

【無料で使えて意外と凄いMapKitの世界】
Apple標準フレームワークでお馴染み「MapKit」ですが、
「ただ地図にピンを立てるだけ」ではなく様々な機能がいくつもあることを、みなさん知っていますか?

実はMapKitには毎年新機能が追加され、年々出来ることも増えてきています。
・スポット名での検索&サジェスト
・A地点からB地点への経路探索
・緯度経度と住所の相互変換(ジオコーディング)
・ピンのカスタマイズやクラスタリング
・地図上のスポットアイコン(POI)のフィルタリング
・マップ上への図形の描画
など、このLTでは実際にプロダクトに導入した経験も踏まえ、
MapKitを使い無料で実現できる様々なカスタマイズや機能についてご紹介します!

saikei (Keisuke Saito)

September 02, 2023
Tweet

More Decks by saikei (Keisuke Saito)

Other Decks in Programming

Transcript

  1. ແྉͰ࢖͑ͯҙ֎ͱੌ͍
    .BQ,JUͷੈք
    χϑςΟϥΠϑελΠϧגࣜձࣾ
    ,FJTVLF4BJUPʢ!TBJLFJʣ
    ʘࠓ೥΋εϙϯαʔ΍ͬͯ·͢ʂʗ
    https://github.com/saikei718/iosdc2023-mapkit
    J04%$+BQBOϧʔΩʔζ-5ʢ%":ʣ

    View full-size slide

  2. ๏J1IPOF04ʢJ04ʣ͔Β࢖͑Δ"QQMFඪ
    ४"1*
    ๏ຖ೥ͷΑ͏ʹ৽ػೳ͕௥Ճ͞Εʮ.BQ,JUͰग़དྷ
    Δ͜ͱʯ͕ͲΜͲΜ૿͍͑ͯΔʂ
    ๏୭Ͱ΋ແྉͰ࢖͑Δʂ
    .BQ,JUͱ͸ʁ
    🙋ࣗݾ঺հʂ

    View full-size slide

  3. 🤔
    .BQ,JU࢖ͬͯ·͔͢ʁ
    🙋ᜊ౻ܓ༞ʢ,FJTVLF4BJUPʣͱ͍͍·͢ʂ

    View full-size slide

  4. ஍ਤΞϓϦ͡Όͳ͍͔Β
    ؔ܎ͳ͍ʁ
    🤨
    🙋9ʢ5XJUUFSʣ͸!TBJLFJͰ΍ͬͯ·͢ʂ

    View full-size slide

  5. ஍ਤ͕ϝΠϯͰ͸ͳͯ͘΋
    ิॿػೳͱͯ͠.BQ,JUΛ
    ׆༻Ͱ͖Δ͔΋͠Ε·ͤΜʂ
    🥳🥳🥳
    🙋J04%$͸೥͕ॳࢀՃͰɺࠓ೥͕ॳΊͯͷΦϑϥΠϯࢀՃͰ͢ʂ

    View full-size slide

  6. ʮJ04%$ΞϓϦ Ծ
    ʹ
    ϨετϥϯΛ୳͢ػೳʯ
    Λ௥Ճ͍ͨ͠ʂ
    🧑💻🧑💻🧑💻
    🙋J04ΤϯδχΞྺ͸೥͘Β͍ʹͳΓ·͢ʂ

    View full-size slide

  7. ॅॴͱҢ౓ܦ౓ͷ૬ޓม׵
    ʢδΦίʔσΟϯάͱٯδΦίʔσΟϯάʣ
    ॅॴ
    FY౦ژ౎৽॓۠େٱอ
    Ң౓ܦ౓
    FY

    $-(FPDPEFS
    J04
    // 住所 → 緯度経度(を含むCLPlacemark)


    let address = "東京都新宿区大久保3-4-1"


    let placemarks: [CLPlacemark] = try await CLGeocoder().geocodeAddressString(address)


    // 緯度経度 → 住所(を含むCLPlacemark)


    let location = CLLocation(latitude: 35.70620, longitude: 139.70713)


    let placemarks: [CLPlacemark] = try await CLGeocoder().reverseGeocodeLocation(location)
    🙋ॴଐ͸ʮχϑςΟϥΠϑελΠϧגࣜձࣾγεςϜ։ൃ෦ΞϓϦ։ൃνʔϜʯͰ͢ʂ

    View full-size slide

  8. Ξϊςʔγϣϯʢϐϯʣ
    Λ஍ਤʹࢗ͢ J04
    🙋ฐࣾ͸ࠓ೥΋J04%$ͷεϙϯαʔΛ͍ͯ͠·͢ʂ
    let annotation = MKPointAnnotation()


    annotation.title = “早稲田大学" // タイトル(任意)


    annotation.subtitle = "西早稲田キャンパス" // タイトル(任意)


    annotation.coordinate = CLLocationCoordinate2D( /* 西早稲田駅 */ )


    self.mapView.addAnnotation(annotation)

    View full-size slide

  9. Ξϊςʔγϣϯʢϐϯʣ
    Λ஍ਤʹࢗ͢ J04
    let annotation = MKPointAnnotation()


    annotation.title = “早稲田大学" // タイトル(任意)


    annotation.subtitle = "西早稲田キャンパス" // タイトル(任意)


    annotation.coordinate = CLLocationCoordinate2D( /* 西早稲田駅 */ )


    self.mapView.addAnnotation(annotation)
    func mapView(


    _ mapView: MKMapView,


    viewFor annotation: MKAnnotation


    ) -> MKAnnotationView? {


    let annotationView = mapView.dequeueReusableAnnotationView(


    withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier,


    for: annotation


    ) as! MKMarkerAnnotationView


    annotationView.clusteringIdentifier = "group1"


    return annotationView


    }
    ΫϥελϦϯάʹରԠͤ͞Δ
    🙋ฐࣾ͸ࠓ೥΋J04%$ͷεϙϯαʔΛ͍ͯ͠·͢ʂ

    View full-size slide

  10. Ξϊςʔγϣϯʢϐϯʣ
    ͷΧελϚΠζ J04
    ΧελϜΞϊςʔγϣϯ
    ˞ΧελϜΞϊςʔγϣϯ͸ʮχϑςΟෆಈ࢈ʢߪೖ൛J04ΞϓϦʣʯΑΓ
    σϑΥϧτΞϊςʔγϣϯ
    🙋ʮπόϝฑͷϚΠΫϩϑΝΠόʔΫϩεʯΛϊϕϧςΟ#09ʹೖΕ͍ͯͨձࣾͰ͢ʂ

    View full-size slide

  11. εϙοτʢ৔ॴʣݕࡧ
    Ωʔϫʔυ
    FYϨετϥϯ
    ʢ˞ج४஍఺͸೚ҙͰઃఆՄʣ
    .,-PDBM4FBSDI$PNQMFUFS
    J04
    let searchCompleter = MKLocalSearchCompleter()


    searchCompleter.delegate = self


    searchCompleter.queryFragment = "レストラン"


    searchCompleter.region = MKCoordinateRegion(/* 西早稲田駅 */)


    // 検索結果は `
    MKLocalSearchCompleterDelegate` で受け取る


    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {


    print("result:\(completer.results)") // -> [MKLocalSearchCompletion]


    }
    🙋໌೔ʢ%":ʣͷʲʙʳ5SBDL$Ͱεϙϯαʔηογϣϯ΋ొஃ͠·͢ʂ

    View full-size slide

  12. Ϛοϓ΁ͷਤܗͷΦʔόʔϨΠʢඳըʣ
    📝7JFX$POUSPMMFS
    J04
    📝.,.BQ7JFX%FMFHBUF
    let coordinates: [CLLocationCoordinate2D] = [


    .init(latitude: 35.70620, longitude: 139.70713),


    // 四角形の4点を緯度経度で指定


    ]


    let polygon = MKPolygon(


    coordinates: coordinates1,


    count: coordinates1.count


    )


    mapView.addOverlay(polygon1)
    func mapView(


    _ mapView: MKMapView,


    rendererFor overlay: MKOverlay


    ) -> MKOverlayRenderer {


    let polygon = overlay as! MKPolygon


    let polygonRenderer = MKPolygonRenderer(polygon: polygon)


    polygonRenderer.strokeColor = .systemRed // 枠の色


    polygonRenderer.lineWidth = 2.0 // 枠の太さ


    polygonRenderer.fillColor = .green // 塗りの色


    polygonRenderer.alpha = 0.5 // 透明度


    return polygonRenderer


    }
    ਤܗͷ௖఺ΛҢ౓ܦ౓Ͱࢦఆ
    .BQ7JFXʹBEE0WFSMBZ͢Δ
    ਤܗͷσβΠϯ͸
    .,.BQ7JFX%FMFHBUFଆͰࢦఆ
    🙋Α͚Ε͹εϙϯαʔηογϣϯ΋ݟʹ͖͍ͯͩ͘͞ʂ

    View full-size slide

  13. ܦ࿏୳ࡧ
    ࢝఺ɾऴ఺
    FYʮ੢ૣҴాӺʯ͔Β
    ʮձ৔ೖΓޱʯ·Ͱ
    J04
    let request = MKDirections.Request()


    request.source = MKMapItem(placemark: MKPlacemark(coordinate: /* 西早稲田駅 */ )) // 始点緯度経度


    request.destination = MKMapItem(placemark: MKPlacemark(coordinate: /* 会場入り口 */ )) // 終点緯度経度


    request.transportType = .walking // 移動手段: MKDirectionsTransportType(徒歩、自動車、公共交通機関、全て)


    let response: MKDirections.Response = try await MKDirections(request: request).calculate()


    print(response.routes) // -> [MKRoute]
    🙋ීஈ͸ϖϯϥΠτৼΔଆͷੜ׆Λ͍ͯ͠ΔΜͰ͕͢ʜ
    .,%JSFDUJPOT

    View full-size slide

  14. Ϛοϓཁૉʢ10*ʣ
    ͷϑΟϧλϦϯά
    σϑΥϧτ
    J04
    ੾Γସ͑ͷ༷ࢠ
    let filter = MKPointOfInterestFilter(including: [.cafe, .restaurant])


    let mapConfiguration = MKStandardMapConfiguration()


    mapConfiguration.pointOfInterestFilter = filter


    self.mapView.preferredConfiguration = mapConfiguration
    🙋ϖϯϥΠτΛৼΒΕΔଆͷޫܠ΋ѱ͘ͳ͍Ͱ͢Ͷʂਪ͕͠ݟͯΔੈքʂ
    .,1PJOU0G*OUFSFTU'JMUFS

    View full-size slide

  15. Ϛοϓཁૉʢ10*ʣ
    ͷ৘ใऔಘ J04
    // λοϓՄೳͳཁૉΛࢦఆ


    mapView.selectableMapFeatures = [.pointsOfInterest, .physicalFeatures, .territories]


    // λοϓΠϕϯτ͸௨ৗͷΞϊςʔγϣϯͱಉ༷ `MKMapViewDelegate` Ͱड͚औΔ


    func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {


    guard let featureAnnotation = annotation as? MKMapFeatureAnnotation else { return }


    let featureRequest = MKMapItemRequest(mapFeatureAnnotation: featureAnnotation)


    Task {


    let featureMapItem: MKMapItem = try await featureRequest.mapItem


    print(featureMapItem.name ?? "nil") // => 早稲田大学 西早稲田キャンパス


    print(featureMapItem.phoneNumber ?? "nil") // => +81 3 3203 4333


    print(featureMapItem.url?.absoluteString ?? "nil") // => https://www.waseda.jp/top/


    }


    }
    TFMFDUBCMF.BQ'FBUVSFTΛࢦఆ
    .,.BQ7JFX%FMFHBUFͰλοϓΛड͚औΔ
    .,.BQ*UFN3FRVFTUʹ౉͢ͱʮి࿩൪߸ʯ
    ΍ʮαΠτͷ63-ʯ͕ฦ٫͞ΕΔ
    🙋GPSUFFͰͷϑΟʔυόοΫ΋͓଴ͪͯ͠·͢ʂ

    View full-size slide

  16. ๏.BQ,JU͸ແྉͰ࢖͑Δ্ʹɺ༷ʑͳػೳ͕͋
    ΓɺΧελϚΠζ΋ग़དྷΔʂ
    ๏஍ਤΞϓϦҎ֎Ͱ΋.BQ,JUͷ׆༻৔໘͸ଟ਺ʂ
    ๏J04͔Β.BQ,JUͷ4XJGU6*ͷαϙʔτ͕֦
    ு͞ΕΔͳͲɺ·ͩ·ͩਐԽΛ͍ͯ͠Δ
    ·ͱΊ
    🙋ᜊ౻ܓ༞ʢ,FJTVLF4BJUPʣ🏢χϑςΟϥΠϑελΠϧגࣜձࣾ
    𝕏
    !TBJLFJ

    View full-size slide

  17. https://github.com/saikei718/iosdc2023-mapkit
    ࠓճͷ-5಺Ͱ͝঺հͨ͠಺༰͸ɺ
    ύϯϑϨοτ QQ
    ʹ΋ܝࡌ͞Ε͍ͯ·͢ʂ
    αϯϓϧΞϓϦ΋(JU)VCͰެ։͍ͯ͠ΔͷͰ
    ͲͪΒ΋ซͤͯ͝ཡ͍ͩ͘͞ʂ
    ΋ͬͱৄ͘͠஌Γ͍ͨํ͸ʜʂ
    🙋͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ ᜊ౻ܓ༞ʢ,FJTVLF4BJUPʣ

    View full-size slide

  18. Ξϊςʔγϣϯʢϐϯʣ
    ͷΧελϚΠζ J04
    ᶃ.,1PJOU"OOPUBUJPO
    wΞϊςʔγϣϯࣗମͷ৘ใΛ
    ࣋ͭΫϥε
    w஍ਤ্ʹ഑ஔ͢Δͷʹ࢖༻͢
    Δ
    ᶄ.,"OOPUBUJPO7JFX
    wΞϊςʔγϣϯͷ7JFXΛ࣋ͭ
    Ϋϥε
    w.BQ7JFX%FMFHBUF಺Ͱᶃͷ
    Ξϊςʔγϣϯʹରͯ͠7JFXΛ
    ࢦఆ͢Δ
    "QQFOEJY
    // マップ上に表示するアノテーションの指定


    let location = CLLocationCoordinate2D(latitude: 35.70620, longitude: 139.70713) let
    annotation = CustomAnnotation()


    annotation.coordinate = location


    mapView.addAnnotation(annotation)


    // カスタマイズアノテーション & アノテーションView


    class CustomAnnotation: MKPointAnnotation {} // アノテーションの情報(空でも可)


    class CustomAnnotationView: MKAnnotationView {} // 実際のアノテーションのView(xibでの実装も可)


    // `
    MapViewDelegate` でアノテーションに対するアノテーションViewを指定する


    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {


    switch annotation {


    case is CustomAnnotation:


    // CustomAnnotationで作成されたアノテーション


    let customAnnotationView = CustomAnnotationView() return customAnnotationView


    case let annotation as MKClusterAnnotation:


    // クラスタリングされたアノテーション


    let clusteringCustomAnnotationView = ClusteringCustomAnnotationView()


    // クラスタリングされているアノテーションの情報は `
    annotation.memberAnnotations` で取得可


    // `
    ClusteringCustomAnnotationView` のプロパティに渡すことで表示を切り替えられる


    return customAnnotationView


    }


    }

    View full-size slide

  19. BJSQPSU
    ʢۭߓʣ
    BNVTFNFOU1BSL
    ʢ༡Ԃ஍ʣ
    BRVBSJVN
    ʢਫ଒ؗʣ
    BUN
    ʢ"5.ʣ
    CBLFSZ
    ʢύϯ԰ʣ
    CBOL
    ʢۜߦʣ
    CFBDI
    ʢϏʔνʣ
    CSFXFSZ
    ʢৢ଄ॴʣ
    DBGF
    ʢΧϑΣʣ
    DBNQHSPVOE
    ʢΩϟϯϓ৔ʣ
    DBS3FOUBM
    ʢϨϯλΧʔʣ
    FW$IBSHFS
    ʢ&7ॆిثʣ
    fi
    SF4UBUJPO
    ʢফ๷ॺʣ
    fi
    UOFTT$FOUFS
    ʢδϜʣ
    GPPE.BSLFU
    ʢ৯ྉ඼ళʣ
    HBT4UBUJPO
    ʢΨιϦϯελϯυʣ
    IPTQJUBM
    ʢපӃʣ
    IPUFM
    ʢϗςϧʣ
    MBVOESZ
    ʢΫϦʔχϯά԰ʣ
    MJCSBSZ
    ʢਤॻؗʣ
    NBSJOB
    ʢߓʣ
    NPWJF5IFBUFS
    ʢөըؗʣ
    NVTFVN
    ʢത෺ؗʣ
    OBUJPOBM1BSL
    ʢࠃཱެԂʣ
    OJHIUMJGF
    ʢόʔͳͲʣ
    QBSL
    ʢެԂʣ
    QBSLJOH
    ʢறं৔ʣ
    QIBSNBDZ
    ʢༀہʣ
    QPMJDF
    ʢܯ࡯ॺʣ
    QPTU0
    ff
    i
    DF
    ʢ༣ศہʣ
    QVCMJD5SBOTQPSU
    ʢެڞަ௨ػؔʣ
    SFTUBVSBOU
    ʢϨετϥϯʣ
    SFTUSPPN
    ʢτΠϨʣ
    TDIPPM
    ʢֶߍʣ
    TUBEJVN
    ʢελδΞϜʣ
    TUPSF
    ʢ͓ళʣ
    UIFBUFS
    ʢܶ৔ʣ
    VOJWFSTJUZ
    ʢେֶʣ
    XJOFSZ
    ʢϫΠφϦʔʣ
    [PP
    ʢಈ෺Ԃʣ
    શछྨ
    10*ͷΧςΰϦʔʢ.,1PJOU0G*OUFSFTU$BUFHPSZʣ
    "QQFOEJY

    View full-size slide