Last Updated:
Photo by henry perks on Unsplash

Integrating MapKit with SwiftUI, 初探

想要在 app 裡加入地圖的功能時,必須使用 MapKit framework 底下的MKMapView class,其定義如下(詳見文末的官方文件連結):

// An embeddable map interface, similar to the one provided by the Maps application.

class MKMapView : UIView

MKMapView是一個 UIView,因此無法直接將它應用在 SwiftUI View 裡面,而是得先透過UIViewRepresentable這個 wrapper 來將它包裝成 SwiftUI 認得的 View(詳見文末的官方文件連結)

// A wrapper for a UIKit view that you use to integrate that view into your SwiftUI view hierarchy.

public protocol UIViewRepresentable : View where Self.Body == Never {
    ...
}

首先,開啟一個新的 app 專案,並在專案底下新增一個 SwiftUI View 檔案,取名為 "MapView.swift"。

  1. 由於會使用到 MapKit,因此在import SwiftUI的下一行必須導入import MapKit
  2. 另外,讓自定的 MapView struct 遵循UIViewRepresentable protocol。
// MapView.swift

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    // more code
}

但Xcode 無法編譯以上程式碼,會出現以下錯誤訊息:
Type 'MapView' does not conform to protocol 'UIViewRepresentable"。所以,我們必須在 "more code" 的位置補上兩個必要的 function,分別是makeUIView(context:)updateUIView(_:context:),並在makeUIView()裡創建一個MKMapView的實例,然後回傳;至於updateUIView()的內容,則暫時先留白沒有關係。

//MapView.swift

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    /// Creates the view object and configures its initial state.
    /// You must implement this method and use it to create your view
    /// object. The system calls this method only once, when it creates
    /// your view for the first time. For all subsequent updates, the
    /// system calls the ``updateUIView(_:context:)`` method.
    func makeUIView(context: Context) -> MKMapView {
        // 1.產生一個MKMapView的instance並且回傳
        let mapView = MKMapView()
        return mapView
    }
    
    /// When the state of your app changes, SwiftUI updates the portions
    /// of your interface affected by those changes. SwiftUI calls this
    /// method for any changes affecting the corresponding UIKit view.
    func updateUIView(_ uiView: MKMapView, context: Context) {
        // 2.暫時留白無妨
    }
}

有一點要注意的是,我們在這兩個 function 的context參數中直接賦予值 "Context",其實 Context 等同於是UIViewRepresentableContext<Self>Self 即是指 MapView )。之所以能簡化為 Context,是因為在 UIViewRepresentable 裡有著這樣的定義(在 Xcode 裡按下 Cmd + Shift + O,查找 UIViewRepresentable )。

public protocol UIViewRepresentable : View where Self.Body == Never {
    ...
    typealias Context = UIViewRepresentableContext
}

至此,我們已可將自定的 MapView 用於 SwiftUI View,讓我們的 app 出現地圖囉!

// ContentView.swift

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            MapView()
                .edgesIgnoringSafeArea(.all)
        }
    }
}

[ 補充資料 ] Apple Developer Documentation

Comments