안녕하세요 현재 swiftUi로 네트워크통신을 통해 받아온 데이터를 핀으로 표시해줬는데
지도 드래그/카메라 이동 이벤트 감지될때 카메라 위치에 따라서 중심값을 위도, 경도로 받아서 다시 네트워크 통신해서 데이터를 핀으로 보여주고 싶습니다
CameraEventHandlerSample을 보면서 만들었는데 드래그할때 작동하지가 않습니다
혹시 제가 작성한 코드에 이상한 점이 있을까요??
SDK 버전 : 2.10.5 / ID: 1131361
import SwiftUI
import UIKit
import KakaoMapsSDK
struct ContentView: View {
@State private var draw: Bool = false
@State private var placeData: KakaoPlace?
@State private var selectedIndex: Int = 0
@State private var selectedCategoryCode: String = CategoryCode.cafe.rawValue
let buttonLabel = ["카페", "음식점", "숙소", "관광지", "병원", "미용"]
var body: some View {
VStack {
placeButtonScrollView()
KakaoMapView(draw: $draw, placeMapData: $placeData, selectedCategoryCode: $selectedCategoryCode)
.onAppear {
print()
self.draw = true
if placeData == nil {
print("Calling fetchInitialData()🥹🥹🥹🥹🥹")
fetchInitialData()
}
}
.onDisappear {
self.draw = false
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
func fetchInitialData() {
print("fetchInitialData")
guard let latitude = Double(UserDefaultsManager.shared.latY),
let longitude = Double(UserDefaultsManager.shared.lonX) else {
print("위치 정보가 저장되어 있지 않습니다.")
return
}
// selectedIndex = 0 // 카페 버튼 선택
// selectedCategoryCode = CategoryCode.cafe.rawValue // 기본 카테고리 카페 설정
fetchDataForCoordinates(latitude: latitude, longitude: longitude, categoryCode: selectedCategoryCode)
}
func placeButtonScrollView() -> some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<buttonLabel.count, id: \.self) { index in
Button(action: {
selectedIndex = index
let category = CategoryCode.category(index: index)
selectedCategoryCode = category.rawValue
print("Button \(index + 1) clicked - Category: \(category)")
guard let latitude = Double(UserDefaultsManager.shared.latY),
let longitude = Double(UserDefaultsManager.shared.lonX) else {
print("위치 정보가 저장되어 있지 않습니다.")
return
}
// 네트워크 요청 보내기
fetchDataForCoordinates(latitude: latitude, longitude: longitude, categoryCode: selectedCategoryCode)
}) {
Text("\(buttonLabel[index])")
.padding()
.background(selectedIndex == index ? Color.white : Color.blue)
.foregroundColor(selectedIndex == index ? Color.blue : Color.white)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
.padding()
}
.frame(height: 50)
}
func fetchDataForCoordinates(latitude: Double, longitude: Double, categoryCode: String) {
let query: String
if selectedIndex <= 3 {
query = "애견동반"
} else if selectedIndex == 4 {
query = "동물"
} else if selectedIndex == 5 {
query = "반려동물미용"
} else {
query = "애견동반"
}
let router = PlaceRouter.searchKeyword(query: query, page: 1, category: categoryCode, sort: "distance", longitude: longitude, latitude: latitude)
PlaceNetworkManager.shared.placeRequest(router: router) { result in
DispatchQueue.main.async {
switch result {
case .success(let placeData):
self.placeData = placeData
// ㅊ. print("Fetched new data: \(placeData)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
}
}
}
struct KakaoMapView: UIViewRepresentable {
@Binding var draw: Bool
@Binding var placeMapData: KakaoPlace?
@Binding var selectedCategoryCode: String
func makeUIView(context: Self.Context) -> KMViewContainer {
let view: KMViewContainer = KMViewContainer(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
context.coordinator.createController(view)
return view
}
func updateUIView(_ uiView: KMViewContainer, context: Self.Context) {
if draw {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if context.coordinator.controller?.isEnginePrepared == false {
context.coordinator.controller?.prepareEngine()
}
if context.coordinator.controller?.isEngineActive == false {
context.coordinator.controller?.activateEngine()
}
if let placeMapData = placeMapData {
print("placeMapData available:☠️☠️☠️☠️☠️ ")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
context.coordinator.addMarkers(placeMapData)
}
} else {
print("placeMapData is nil👎🏻👎🏻👎🏻👎🏻👎🏻👎🏻👎🏻👎🏻👎🏻")
}
}
} else {
context.coordinator.controller?.pauseEngine()
context.coordinator.controller?.resetEngine()
}
}
func makeCoordinator() -> KakaoMapCoordinator {
return KakaoMapCoordinator(selectedCategoryCode: selectedCategoryCode)
}
static func dismantleUIView(_ uiView: KMViewContainer, coordinator: KakaoMapCoordinator) {
}
class KakaoMapCoordinator: NSObject, MapControllerDelegate {
var longitude = Double(UserDefaultsManager.shared.lonX) ?? 0.0
var latitude = Double(UserDefaultsManager.shared.latY) ?? 0.0
var controller: KMController?
var container: KMViewContainer?
var first: Bool
var auth: Bool
var selectedCategoryCode: String = CategoryCode.cafe.rawValue
var existingPoiIDs: [String] = []
var cameraStartHandler: DisposableEventHandler?
var cameraStoppedHandler: DisposableEventHandler?
var _defaultCameraPosition: CameraUpdate?
init(selectedCategoryCode: String) {
self.first = true
self.auth = false
self.selectedCategoryCode = selectedCategoryCode
super.init()
}
func removeExistingMarkers() {
guard let kakaoMap = controller?.getView("mapview") as? KakaoMap else {
return
}
guard let layer = kakaoMap.getLabelManager().getLabelLayer(layerID: "PoiLayer") else {
return
}
for poiID in existingPoiIDs {
layer.removePoi(poiID: poiID) {
print("기존 마커 제거 완료: \(poiID)")
}
}
existingPoiIDs.removeAll()
}
func createController(_ view: KMViewContainer) {
container = view
controller = KMController(viewContainer: view)
controller?.delegate = self
addCameraEventHandlers()
}
func addCameraEventHandlers() {
guard let view = controller?.getView("mapview") as? KakaoMap else {
print("KakaoMap 객체를 가져오지 못했습니다.")
return
}
cameraStoppedHandler = view.addCameraStoppedEventHandler(target: self) { param in
// KakaoMap 객체를 직접 가져오기
if let mapView = self.controller?.getView("mapview") as? KakaoMap {
// 카메라가 멈추면 좌표 가져오기
let position = mapView.getPosition(CGPoint(x: 0.5, y: 0.5))
print("카메라 이동 멈춤. 현재 위치: 경도: \(position.wgsCoord.longitude), 위도: \(position.wgsCoord.latitude)")
// 새로운 좌표로 네트워크 요청
self.moveToLocationAndFetchData(latitude: position.wgsCoord.latitude, longitude: position.wgsCoord.longitude)
} else {
print("Failed to get KakaoMap view")
}
}
}
func createPois() {
print("createPois")
let view = controller?.getView("mapview") as! KakaoMap
let manager = view.getLabelManager()
let layer = manager.getLabelLayer(layerID: "PoiLayer")
layer?.visible = true
let poiOption = PoiOptions(styleID: "PerLevelStyle")
poiOption.rank = 0
poiOption.clickable = true
let poi1 = layer?.addPoi(option: poiOption, at: MapPoint(longitude: longitude, latitude: latitude))
poi1?.show()
layer?.showAllPois()
print("Poi created at: \(longitude), \(latitude)")
}
func addViews() {
let defaultPosition: MapPoint = MapPoint(longitude: longitude, latitude: latitude)
let mapviewInfo: MapviewInfo = MapviewInfo(viewName: "mapview", viewInfoName: "map", defaultPosition: defaultPosition)
if controller?.addView(mapviewInfo) == nil {
print("지도 뷰 추가 실패")
} else {
controller?.addView(mapviewInfo)
print("지도 뷰 추가 성공")
}
}
// 지도 드래그/카메라 이동 이벤트 감지
func mapView(_ mapView: KMViewContainer, cameraDidMoveTo coord: MapPoint) {
let currentLatitude = coord.wgsCoord.latitude
let currentLongitude = coord.wgsCoord.longitude
print("지도 중심 좌표가 변경되었습니다. 위도: \(currentLatitude), 경도: \(currentLongitude)")
// 지도 드래그가 끝나면 새로운 좌표로 네트워크 요청을 보내 데이터를 업데이트
moveToLocationAndFetchData(latitude: currentLatitude, longitude: currentLongitude)
}
// 새로운 좌표에 대한 네트워크 요청
func fetchDataForCoordinates(latitude: Double, longitude: Double, categoryCode: String) {
let query: String
switch selectedCategoryCode {
case "cafe", "restaurant", "hotel", "tourist":
query = "애견동반"
case "hospital":
query = "동물"
case "beauty":
query = "반려동물미용"
default:
query = "애견동반"
}
let router = PlaceRouter.searchKeyword(query: query, page: 1, category: categoryCode, sort: "distance", longitude: longitude, latitude: latitude)
PlaceNetworkManager.shared.placeRequest(router: router) { result in
DispatchQueue.main.async {
switch result {
case .success(let placeData):
//print("새로운 좌표에 대한 데이터 가져오기 완료: \(placeData)")
// 데이터를 가져온 후 마커 업데이트
self.updateMapWithNewData(placeData)
case .failure(let error):
print("데이터 가져오기 실패: \(error.localizedDescription)")
}
}
}
}
func updateMapWithNewData(_ placeData: KakaoPlace) {
// 새로운 데이터를 받아서 지도에 마커 업데이트
print("updateMapWithNewData")
removeExistingMarkers()
addMarkers(placeData)
}
func addMarkers(_ placeData: KakaoPlace) {
removeExistingMarkers()
createTextStyle()
guard let view = controller?.getView("mapview") as? KakaoMap else {
print("Failed to get KakaoMap view or mapview is not KakaoMap type")
return
}
let manager = view.getLabelManager()
var layer = manager.getLabelLayer(layerID: "PoiLayer")
if layer == nil {
let layerOption = LabelLayerOptions(layerID: "PoiLayer", competitionType: .none, competitionUnit: .symbolFirst, orderType: .rank, zOrder: 10001)
layer = manager.addLabelLayer(option: layerOption)
}
// 새로운 마커 추가
for place in placeData.documents {
guard let longitude = Double(place.x), let latitude = Double(place.y) else {
print("Invalid coordinates for place: \(place.placeName)")
continue
}
print("Adding marker for place: \(place.placeName), at \(longitude), \(latitude)")
let poiID = "poi_\(place.id)"
let poiOption = PoiOptions(styleID: "customStyle1", poiID: poiID)
poiOption.rank = 0
poiOption.addText(PoiText(text: place.placeName, styleIndex: 0))
let poi = layer?.addPoi(option: poiOption, at: MapPoint(longitude: longitude, latitude: latitude))
poi?.show()
// 새로운 마커 ID 저장
existingPoiIDs.append(poiID)
}
}
func createTextStyle() {
guard let view = controller?.getView("mapview") as? KakaoMap else {
print("Failed to get KakaoMap view or mapview is not KakaoMap type")
return
}
let manager = view.getLabelManager()
let resizedImage = UIImage(named: "pin")?.resize(to: CGSize(width: 24, height: 24))
let iconStyle = PoiIconStyle(symbol: resizedImage, anchorPoint: CGPoint(x: 0.0, y: 0.5))
var textLineStyles = [PoiTextLineStyle]()
for i in 0...4 {
let textStyle = TextStyle(
fontSize: 20,
fontColor: UIColor.white,
strokeThickness: 2,
strokeColor: UIColor.black,
//font: "Noto Sans KR Bold",
charSpace: i,
lineSpace: 1.0 + Float(i) * 0.2,
aspectRatio: 0.6 + Float(i) * 0.2
)
let textLineStyle = PoiTextLineStyle(textStyle: textStyle)
textLineStyles.append(textLineStyle)
}
let poiTextStyle = PoiTextStyle(textLineStyles: textLineStyles)
poiTextStyle.textLayouts = [.center]
let poiStyle = PoiStyle(styleID: "customStyle1", styles: [PerLevelPoiStyle(iconStyle: iconStyle, textStyle: poiTextStyle, level: 0)])
manager.addPoiStyle(poiStyle)
}
func addViewSucceeded(_ viewName: String, viewInfoName: String) {
print("OK")
let view = controller?.getView("mapview")
view?.viewRect = container!.bounds
}
func containerDidResized(_ size: CGSize) {
let mapView: KakaoMap? = controller?.getView("mapview") as? KakaoMap
mapView?.viewRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
if first {
let cameraUpdate: CameraUpdate = CameraUpdate.make(target: MapPoint(longitude: longitude, latitude: latitude), mapView: mapView!)
mapView?.moveCamera(cameraUpdate)
first = false
}
}
func authenticationSucceeded() {
auth = true
}
func setDefaultOptions() {// 정북방향으로 카메라 방향을 초기화시킨다.
print("처음위치으로 이동!!!")
guard let view = controller?.getView("mapview") as? KakaoMap else {
print("Failed to get KakaoMap view or mapview is not KakaoMap type")
return
}
view.cameraAnimationEnabled = true //카메라 애니메이션 활성화. 애니메이션을 비활성화하면 animateCamera를 호출해도 moveCamera 처럼 애니메이션 없이 바로 이동한다.
view.setBackToNorthDuringAutoElevation(true) //활성화시 Auto Elevation 동작시 정북방향으로 카메라 방향을 초기화시킨다.
_defaultCameraPosition = CameraUpdate.make(mapView: view) //현재 뷰의 카메라 위치를 기억하는 CameraUpdate 객체를 생성.
}
func moveToCurrentLocation() { //현재위치로 지도 카메라 이동
guard let latitude = Double(UserDefaultsManager.shared.latY),
let longitude = Double(UserDefaultsManager.shared.lonX) else {
print("유저 위치 정보를 불러오지 못했습니다.")
return
}
guard let view = controller?.getView("mapview") as? KakaoMap else {
print("Failed to get KakaoMap view or mapview is not KakaoMap type")
return
}
let cameraUpdate = CameraUpdate.make(target: MapPoint(longitude: longitude, latitude: latitude), zoomLevel: 15, mapView: view)
// 카메라 애니메이션과 함께 현재 위치로 이동
view.animateCamera(cameraUpdate: cameraUpdate, options: CameraAnimationOptions(autoElevation: true, consecutive: true, durationInMillis: 2000))
}
// 지도 이동 후 새로운 위치로 데이터 요청 및 마커 업데이트
func moveToLocationAndFetchData(latitude: Double, longitude: Double) {
// guard let kakaoMap = controller?.getView(“mapview”) as? KakaoMap else {
// print(“카카오 맵을 찾을 수 없습니다.”)
// return
// }
//
// let cameraUpdate = CameraUpdate.make(target: MapPoint(longitude: longitude, latitude: latitude), zoomLevel: 15, mapView: kakaoMap)
//
// // 카메라 애니메이션으로 새로운 위치로 이동
// kakaoMap.animateCamera(cameraUpdate: cameraUpdate, options: CameraAnimationOptions(autoElevation: true, consecutive: true, durationInMillis: 2000)) {
// // 카메라 이동 완료 후 네트워크 요청
// print(“카메라 이동 완료! 새로운 좌표에 대한 네트워크 요청 시작”)
// self.fetchDataForCoordinates(latitude: latitude, longitude: longitude, categoryCode: self.selectedCategoryCode)
// }
fetchDataForCoordinates(latitude: latitude, longitude: longitude, categoryCode: selectedCategoryCode)
}
}
}
extension UIImage {
func resize(to size: CGSize) → UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
self.draw(in: CGRect(origin: .zero, size: size))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resizedImage
}
}