EventHandling

KakaoMapsSDK의 EventHandling 에 대한 설명.

KakaoMapsSDK 에서는 지도 내에서 발생하는 다양한 동작에 대해서 이벤트 처리를 위한 EventDelegate, 혹은 Event handler를 제공합니다.

EventDelegate


EventDelegate를 통해 지도 내에서 발생하는 이벤트를 일괄적으로 delegate protocol을 통해 핸들링 할 수 있습니다. delegate는 필수적인것을 제외하고는 대부분이 optional로 구성되어 있으므로, 필요한 함수를 구현하여 사용할 수 있습니다.

아래 예제는 KakaoMapEventDelegate를 이용하여, 탭한 지점에 poi를 표시하는 간략한 예제입니다.

// KakaoMapEventDelgate를 이용하여 terrain이 탭 되었을 때 action을 정의한다.
class PoiClickServiceSample: APISampleBaseViewController, KakaoMapEventDelegate {

    // terrain이 탭되면 호출.
    func terrainDidTapped(kakaoMap: KakaoMap, position: MapPoint) {
        let mapView: KakaoMap = mapController?.getView("mapview") as! KakaoMap
        let manager = mapView.getLabelManager()
        let layer = manager.getLabelLayer(layerID: "example_click_service")
        let option = PoiOptions(styleID: "label_default_style")
        option.clickable = true
        
        let poi = layer?.addPoi(option: option, at: position)
        poi?.show()
    }
}

Event Handler


일부 이벤트는 Delegate외에 객체마다 개별 이벤트를 전달받을 수 있는 Event Handler를 제공합니다. 객체마다 별도로 동작하는 EventHandler를 추가하여, 생성한 객체마다 다른 이벤트 핸들링을 정의할 수 있습니다. 아래 예제는 Poi를 생성하여, 해당 Poi에 이벤트 핸들러를 추가하여 Poi를 클릭했을 때의 동작을 정의하는 예제입니다.

    // POI를 생성한다.
    func createPois() {
        let view = mapController?.getView("mapview") as! KakaoMap
        let manager = view.getLabelManager()
        let layer = manager.getLabelLayer(layerID: "PoiLayer")   // 생성한 POI를 추가할 레이어를 가져온다.
        let poiOption = PoiOptions(styleID: "customStyle1") // 생성할 POI의 Option을 지정하기 위한 자료를 담는 클래스를 생성. 사용할 스타일의 ID를 지정한다.
        poiOption.rank = 0
        poiOption.clickable = true // clickable 옵션을 true로 설정한다. default는 false로 설정되어있다.
        
        let poi1 = layer?.addPoi(option: poiOption, at: MapPoint(longitude: 127.108678, latitude: 37.402001))   //레이어에 지정한 옵션 및 위치로 POI를 추가한다.
        let _ = poi1?.addPoiTappedEventHandler(target: self, handler: SimplePOI.poiTappedHandler) // poi tap event handler를 추가한다.
        poi1?.show()
    }
    
    // POI 탭 이벤트가 발생하고, 표시하고 있던 Poi를 숨긴다.
    func poiTappedHandler(_ param: PoiInteractionEventParam) {
        param.poiItem.hide()
    }

Callback


KakaoMapsSDK 에서는 지도 위에 Label, Shape, Route 종류의 객체를 지도 위에 추가/삭제할 때 작업이 완료된 시점에 호출할 수 있는 callback 파라미터를 제공합니다. 또한, 카메라의 이동이 완료되었을 때 호출할 수 있는 callback 파라미터를 제공합니다. 객체를 추가하거나 삭제할 때 , 혹은 카메라를 이동할 때 파라미터에 콜백 함수를 함께 전달하여 작업이 완료된 시점에서 콜백함수를 호출하게 만들 수 있습니다.

Camera 이동 관련 callback

camera 이동 관련 API 호출 시 파라미터로 콜백을 전달할 수 있습니다. 이동이 끝나고 나면, 파라미터로 전달된 콜백이 호출됩니다. 아래 예제는 animateCamera API에 콜백을 전달하여, animateCamera 동작이 완료되고 나서 호출되는 콜백을 이용한 예제입니다.

class ApiCallbackSample: APISampleBaseViewController {
    
    override func addViews() {
        let defaultPosition: MapPoint = MapPoint(longitude: 126.978365, latitude: 37.566691)
        let mapviewInfo: MapviewInfo = MapviewInfo(viewName: "mapview", viewInfoName: "map", defaultPosition: defaultPosition)
        
        mapController?.addView(mapviewInfo)
    }
    
    func createPois() {
        // Poi가 속할 레이어 생성
        let mapView: KakaoMap? = mapController?.getView("mapview") as? KakaoMap
        let labelManager = mapView?.getLabelManager()
        let layer = labelManager?.addLabelLayer(option: LabelLayerOptions(layerID: "PoiLayer", competitionType: .none, competitionUnit: .poi, orderType: .rank, zOrder: 0))
        
        // Poi가 그려질 스타일 생성
        let iconStyle = PoiIconStyle(symbol: UIImage(named: "mapIcoBookmark_01.png")!, anchorPoint: CGPoint(x: 0.0, y: 0.5))
        let perLevelStyle = PerLevelPoiStyle(iconStyle: iconStyle, level: 0)
        let poiStyle = PoiStyle(styleID: "PoiStyle1", styles: [perLevelStyle])
        labelManager?.addPoiStyle(poiStyle)
        
        // Pois 생성
        let coord = MapPoint(longitude: 126.875658, latitude: 37.492889)
        let boundary = GeoCoordinate(longitude: 0.026949, latitude: 0.035932)
        let options = PoiOptions(styleID: "PoiStyle1")
        
        var positions = [MapPoint]()
        for _ in 1 ... 100 {
            let rand = GeoCoordinate(
                longitude: Double.random(in: 0...boundary.longitude),
                latitude: Double.random(in: 0...boundary.latitude))
            
            positions.append(MapPoint(longitude: coord.wgsCoord.longitude + rand.longitude,
                                      latitude: coord.wgsCoord.latitude + rand.latitude))
        }
        
        // Pois 추가 -> 카메라 애니메이션 -> show Pois
        let _  = layer?.addPois(option: options, at: positions) { [weak mapView, weak layer](pois: [Poi]?) -> Void in //addPois 완료 콜백 추가
            guard let view = mapView else { return }
            
            if let items = pois {
                let rect = AreaRect(points: items.map{ $0.position }) // 추가된 poi를 기반으로 areaRect를 생성하여 카메라를 이동시킨다.
                view.animateCamera( cameraUpdate: CameraUpdate.make(area: rect), options: CameraAnimationOptions(autoElevation: false, consecutive: false, durationInMillis: 2000)) { () -> Void in
                    guard let labelLayer = layer else { return }
                    labelLayer.showAllPois()    // 카메라 이동이 완료되면 layer에 추가된 모든 poi를 표시한다.
                }
            }
        }
    }
}

객체 추가/삭제시 callback

예를 들어, Poi의 경우 LabelLayer의 Poi를 추가하는 함수에서 아래와 같이 optional로 callback함수를 파라미터에 추가할 수 있습니다.

  • addPoi(option:at:callback:)-> Poi
  • addPois(option:at:callback:) -> [Poi]
  • addPois(options:at:callback:) -> [Poi]

추가한 Poi만큼 생성이 완료되면, 파라미터로 전달된 콜백 함수를 호출합니다. 콜백 함수는 생성한 Poi를 인자로 갖습니다. 아래 예제는 여러개의 Poi 추가가 완료되면, 그 Poi를 포함하는 카메라 영역으로 이동 한 후 Poi를 보여주는 예제입니다.

    func createPois() {
        // Poi가 속할 레이어 생성
        let mapView: KakaoMap? = mapController?.getView("mapview") as? KakaoMap
        let labelManager = mapView?.getLabelManager()
        let layer = labelManager?.addLabelLayer(option: LabelLayerOptions(layerID: "PoiLayer", competitionType: .none, competitionUnit: .poi, orderType: .rank, zOrder: 0))
        
        // Poi가 그려질 스타일 생성
        let iconStyle = PoiIconStyle(symbol: UIImage(named: "mapIcoBookmark_01.png")!, anchorPoint: KMNormalizedPoint(x: 0.0, y: 0.5))
        let perLevelStyle = PerLevelPoiStyle(iconStyle: iconStyle, level: 0)
        let poiStyle = PoiStyle(styleID: "PoiStyle1", styles: [perLevelStyle])
        labelManager?.addPoiStyle(poiStyle)
        
        // Pois 생성
        let coord = MapPoint(longitude: 126.875658, latitude: 37.492889)
        let boundary = GeoCoordinate(longitude: 0.026949, latitude: 0.035932)
        let options = PoiOptions(styleID: "PoiStyle1")
        
        var positions = [MapPoint]()
        for _ in 1 ... 100 {
            let rand = GeoCoordinate(
                longitude: Double.random(in: 0...boundary.longitude),
                latitude: Double.random(in: 0...boundary.latitude))
            
            positions.append(MapPoint(longitude: coord.wgsCoord.longitude + rand.longitude,
                                      latitude: coord.wgsCoord.latitude + rand.latitude))
        }
        
        // Pois 추가 -> 카메라 애니메이션 -> show Pois
        let _  = layer?.addPois(option: options, at: positions) { [weak mapView, weak layer](pois: [Poi]?) -> Void in //addPois 완료 콜백 추가
            guard let view = mapView else { return }
            
            if let items = pois {
                let rect = AreaRect(points: items.map{ $0.position }) // 추가된 poi를 기반으로 areaRect를 생성하여 카메라를 이동시킨다.
                view.animateCamera(cameraUpdate: CameraUpdate.make(area: rect), options: CameraAnimationOptions(autoElevation: false, consecutive: false, durationInMillis: 2000)) { () -> Void in
                    guard let labelLayer = layer else { return }
                    labelLayer.showAllPois()    // 카메라 이동이 완료되면 layer에 추가된 모든 poi를 표시한다.
                }
            }
        }
    }

위와 같은 종류의 callback은 Gui를 제외한 모든 type의 객체(Label, Shape, Route)에 추가할 수 있습니다.

Callback사용시 주의사항

API에서는 위와 같은 형태의 콜백을 optional로 받을 수 있게 되어있으나, 이러한 형태의 callback을 기반으로 코드를 구성하는것은 권장하지 않습니다. 기본적으로 지도 엔진은 사용자가 API를 호출한 순서대로 동작의 시퀀스가 보장됩니다. 예를 들어, addPoi를 한 이후 showPoi를 호출한 경우, 반드시 Poi가 추가된 이후에 Poi가 화면에 표시됩니다. 따라서, 특수한 경우가 아닌 이상 위와 같은 종류의 callback함수는 사용하지 않는것을 권장합니다.