네이버 검색 API + 네이버맵 API 연동해서 식당 찾기
우선 Store 모델 정의
//
// Store.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/11/24.
//
import Foundation
struct StoreResponse: Codable {
let items: [Store]
}
struct Store: Codable {
let name: String
let latitude: Double?
let longitude: Double?
let address: String
enum CodingKeys: String, CodingKey {
case name = "title"
case latitude = "mapy"
case longitude = "mapx"
case address = "address"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
if let latitudeString = try? container.decode(String.self, forKey: .latitude),
let latitude = Double(latitudeString) {
self.latitude = latitude / 10_000_000.0 // 위도 값을 올바르게 변환
} else {
self.latitude = nil
}
if let longitudeString = try? container.decode(String.self, forKey: .longitude),
let longitude = Double(longitudeString) {
self.longitude = longitude / 10_000_000.0 // 경도 값을 올바르게 변환
} else {
self.longitude = nil
}
address = try container.decode(String.self, forKey: .address)
}
}
Swift
복사
그리고 ViewController
//
// ViewController.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/11/24.
//
import UIKit
import NMapsMap
class ViewController: UIViewController {
var mapView: NMFMapView!
var naverAPIManager = NaverAPIManager()
var searchTextField: UITextField!
var markers: [NMFMarker] = []
var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
setupSearchTextField()
setupActivityIndicator()
}
func setupMapView() {
mapView = NMFMapView(frame: view.frame)
view.addSubview(mapView)
mapView.positionMode = .direction
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: 37.5665, lng: 126.9780))
mapView.moveCamera(cameraUpdate)
}
func setupSearchTextField() {
searchTextField = UITextField(frame: CGRect(x: 20, y: 50, width: view.frame.width-40, height: 40))
searchTextField.placeholder = "Search"
searchTextField.borderStyle = .roundedRect
searchTextField.backgroundColor = .white
searchTextField.delegate = self
view.addSubview(searchTextField)
}
func setupActivityIndicator() {
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
func startLoading() {
activityIndicator.startAnimating()
}
func stopLoading() {
activityIndicator.stopAnimating()
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
if let searchText = textField.text {
print("Searching for: \(searchText)")
startLoading()
performSearch(query: searchText)
}
return true
}
func performSearch(query: String) {
naverAPIManager.fetchStores(query: query) { [weak self] stores in
guard let self = self else {
print("Self is nil")
self?.stopLoading()
return
}
guard let stores = stores else {
print("No stores found")
DispatchQueue.main.async {
self.stopLoading()
}
return
}
print("Found \(stores.count) stores")
DispatchQueue.main.async {
self.addMarkers(for: stores)
self.stopLoading()
}
}
}
func addMarkers(for stores: [Store]) {
// Clear existing markers
for marker in markers {
marker.mapView = nil
}
markers.removeAll()
// Add new markers
for store in stores {
if let lat = store.latitude, let lng = store.longitude {
let marker = NMFMarker()
marker.position = NMGLatLng(lat: lat, lng: lng)
marker.captionText = store.name
marker.mapView = self.mapView
print("Marker added for: \(store.name) at Lat: \(lat), Lng: \(lng), Address: \(store.address)")
} else {
print("Failed to convert coordinates to Double for store: \(store.name)")
}
}
}
}
Swift
복사
//
// NaverAPIManager.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/12/24.
//
import Foundation
class NaverAPIManager {
let clientId = "클라이언트ID"
let clientSecret = "클라이언트Secret"
func fetchStores(query: String, completion: @escaping ([Store]?) -> Void) {
let urlString = "https://openapi.naver.com/v1/search/local.json?query=\(query)&display=20"
guard let url = URL(string: urlString) else {
print("Invalid URL")
completion(nil)
return
}
var request = URLRequest(url: url)
request.addValue(clientId, forHTTPHeaderField: "X-Naver-Client-Id")
request.addValue(clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error fetching data: \(error)")
completion(nil)
return
}
guard let data = data else {
print("No data received")
completion(nil)
return
}
do {
let storeResponse = try JSONDecoder().decode(StoreResponse.self, from: data)
completion(storeResponse.items)
} catch {
print("Error decoding data: \(error)")
completion(nil)
}
}
task.resume()
}
}
Swift
복사
여기서 performSearch() 함수를 통해서 검색한 값을 쿼리문에 넣어서 가져온 정보를
네이버 맵 지도에 마커로 표시한다!
그러면 이렇게 키워드에 맞는 장소를 검색할 수 있다.
이제 내 현재위치에서 시작하고 싶다면
1.
CoreLocation 프레임워크 추가
a.
ViewController에서 현재위치를 가져오기 위해 CoreLocation 프레임워크 추가한다.
import CoreLocation
Swift
복사
2.
CLLocationManager 설정
a.
위치 권한을 요청하고, 위치 업데이트를 받기 위해 CLLocationManager 를 설정한다.
3.
현재위치로 지도를 초기화
a.
viewDidLoad에 현재위치를 가져온 후 지도 위치를 업데이트
//
// ViewController.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/11/24.
//
import UIKit
import NMapsMap
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var mapView: NMFMapView!
var naverAPIManager = NaverAPIManager()
var searchTextField: UITextField!
var markers: [NMFMarker] = []
var activityIndicator: UIActivityIndicatorView!
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
setupSearchTextField()
setupActivityIndicator()
setupLocationManager()
}
func setupLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func setupMapView() {
mapView = NMFMapView(frame: view.frame)
mapView.positionMode = .direction
view.addSubview(mapView)
}
func setupSearchTextField() {
searchTextField = UITextField(frame: CGRect(x: 20, y: 50, width: view.frame.width-40, height: 40))
searchTextField.placeholder = "Search"
searchTextField.borderStyle = .roundedRect
searchTextField.backgroundColor = .white
searchTextField.delegate = self
view.addSubview(searchTextField)
}
func setupActivityIndicator() {
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
func startLoading() {
activityIndicator.startAnimating()
}
func stopLoading() {
activityIndicator.stopAnimating()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
updateMapViewLocation(location: location)
locationManager.stopUpdatingLocation()
}
}
func updateMapViewLocation(location: CLLocation) {
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: location.coordinate.latitude, lng: location.coordinate.longitude))
mapView.moveCamera(cameraUpdate)
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
if let searchText = textField.text {
print("Searching for: \(searchText)")
startLoading()
performSearch(query: searchText)
}
return true
}
func performSearch(query: String) {
naverAPIManager.fetchStores(query: query) { [weak self] stores in
guard let self = self else {
print("Self is nil")
self?.stopLoading()
return
}
guard let stores = stores else {
print("No stores found")
DispatchQueue.main.async {
self.stopLoading()
}
return
}
print("Found \(stores.count) stores")
if stores.count < 20 {
print("Expected more results. Received only \(stores.count) results")
}
DispatchQueue.main.async {
self.addMarkers(for: stores)
self.stopLoading()
}
}
}
func addMarkers(for stores: [Store]) {
// Clear existing markers
for marker in markers {
marker.mapView = nil
}
markers.removeAll()
// Add new markers
for store in stores {
if let lat = store.latitude, let lng = store.longitude {
let marker = NMFMarker()
marker.position = NMGLatLng(lat: lat, lng: lng)
marker.captionText = store.name
marker.mapView = self.mapView
print("Marker added for: \(store.name) at Lat: \(lat), Lng: \(lng), Address: \(store.address)")
} else {
print("Failed to convert coordinates to Double for store: \(store.name)")
}
}
}
}
Swift
복사
//
// NaverAPIManager.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/12/24.
//
import Foundation
class NaverAPIManager {
let clientId = "클라이언트ID"
let clientSecret = "클라이언트Secret"
func fetchStores(query: String, completion: @escaping ([Store]?) -> Void) {
let urlString = "https://openapi.naver.com/v1/search/local.json?query=\(query)&display=20"
guard let url = URL(string: urlString) else {
print("Invalid URL")
completion(nil)
return
}
print("Request URL: \(urlString)")
var request = URLRequest(url: url)
request.addValue(clientId, forHTTPHeaderField: "X-Naver-Client-Id")
request.addValue(clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error fetching data: \(error)")
completion(nil)
return
}
guard let data = data else {
print("No data received")
completion(nil)
return
}
do {
let storeResponse = try JSONDecoder().decode(StoreResponse.self, from: data)
completion(storeResponse.items)
} catch {
print("Error decoding data: \(error)")
completion(nil)
}
}
task.resume()
}
}
Swift
복사
이렇게 현재위치가 나온다.
전체코드
//
// ViewController.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/11/24.
//
import UIKit
import NMapsMap
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var mapView: NMFMapView!
var naverAPIManager = NaverAPIManager()
var searchTextField: UITextField!
var markers: [NMFMarker] = []
var activityIndicator: UIActivityIndicatorView!
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
setupSearchTextField()
setupActivityIndicator()
setupLocationManager()
}
func setupLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func setupMapView() {
mapView = NMFMapView(frame: view.frame)
mapView.positionMode = .direction
view.addSubview(mapView)
}
func setupSearchTextField() {
searchTextField = UITextField(frame: CGRect(x: 20, y: 50, width: view.frame.width-40, height: 40))
searchTextField.placeholder = "Search"
searchTextField.borderStyle = .roundedRect
searchTextField.backgroundColor = .white
searchTextField.delegate = self
view.addSubview(searchTextField)
}
func setupActivityIndicator() {
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
func startLoading() {
activityIndicator.startAnimating()
}
func stopLoading() {
activityIndicator.stopAnimating()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
updateMapViewLocation(location: location)
locationManager.stopUpdatingLocation()
}
}
func updateMapViewLocation(location: CLLocation) {
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: location.coordinate.latitude, lng: location.coordinate.longitude))
mapView.moveCamera(cameraUpdate)
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
if let searchText = textField.text {
print("Searching for: \(searchText)")
startLoading()
performSearch(query: searchText)
}
return true
}
func performSearch(query: String) {
naverAPIManager.fetchStores(query: query) { [weak self] stores in
guard let self = self else {
print("Self is nil")
self?.stopLoading()
return
}
guard let stores = stores else {
print("No stores found")
DispatchQueue.main.async {
self.stopLoading()
}
return
}
print("Found \(stores.count) stores")
if stores.count < 20 {
print("Expected more results. Received only \(stores.count) results")
}
DispatchQueue.main.async {
self.addMarkers(for: stores)
self.stopLoading()
}
}
}
func addMarkers(for stores: [Store]) {
// Clear existing markers
for marker in markers {
marker.mapView = nil
}
markers.removeAll()
// Add new markers
for store in stores {
if let lat = store.latitude, let lng = store.longitude {
let marker = NMFMarker()
marker.position = NMGLatLng(lat: lat, lng: lng)
marker.captionText = store.name
marker.mapView = self.mapView
print("Marker added for: \(store.name) at Lat: \(lat), Lng: \(lng), Address: \(store.address)")
} else {
print("Failed to convert coordinates to Double for store: \(store.name)")
}
}
}
}
Swift
복사
//
// NaverAPIManager.swift
// EvMaps
//
// Created by Hyungjun KIM on 7/12/24.
//
import Foundation
class NaverAPIManager {
let clientId = Bundle.main.CLIENT_ID
let clientSecret = Bundle.main.CLIENT_SECRET
func fetchStores(query: String, completion: @escaping ([Store]?) -> Void) {
let urlString = "https://openapi.naver.com/v1/search/local.json?query=\(query)&display=20"
guard let url = URL(string: urlString) else {
print("Invalid URL")
completion(nil)
return
}
print("Request URL: \(urlString)")
var request = URLRequest(url: url)
request.addValue(clientId, forHTTPHeaderField: "X-Naver-Client-Id")
request.addValue(clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error fetching data: \(error)")
completion(nil)
return
}
guard let data = data else {
print("No data received")
completion(nil)
return
}
do {
let storeResponse = try JSONDecoder().decode(StoreResponse.self, from: data)
completion(storeResponse.items)
} catch {
print("Error decoding data: \(error)")
completion(nil)
}
}
task.resume()
}
}
Swift
복사