MapKit
MKMapView를 활용한 맵뷰 구현하기
1. import MapKit
2. MKMapView 인스턴스 생성
3. MKMapView의 region과 annotation 설정
- center: 맵뷰의 중심이 되는 좌표.
- latitudinalMeters, longitudinalMeters: 축적.
- removeAnnotations([Annotation]): 맵뷰의 모든 Annotation을 삭제.
extension MapViewController {
func configureMapView() {
if let coordinate = LocationManager.shared.currenCoordinate {
let region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 5000,
longitudinalMeters: 5000
)
mapView.setRegion(region, animated: true)
}
}
func configureAnnotation() {
mapView.removeAnnotations(mapView.annotations)
showCinemaList.forEach {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: $0.0.0, longitude: $0.0.1)
annotation.title = $0.1
mapView.addAnnotation(annotation)
}
}
}
4. MKMapViewDelegate 구현
extension MapViewController: MKMapViewDelegate {
// 지도를 움직일때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(#function)
}
// 지도의 핀이나 화면을 클릭할 때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
print(#function)
}
// 핀의 이미지를 커스텀하게 구성할때 사용하는 메서드
// 1) Custom MKAnnotationView를 구현
// 2) MKMapView 인스턴스에 register
// 3) 테이블뷰처럼 dequeue해서 사용
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// print(#function)
// }
}
5. MKMapView 인스턴스의 delegate 연결
Custom Annotation 구현 방법
1. MKMapViewDelegate의 mapView(_ mapView:, viewFor annotation:) -> MKAnnotationView? 활용.
2. 위에서 MKAnnotationView를 반환하므로, 이를 상속한 CustomAnnotationView를 구현.
3. 구현한 CustomAnnotationView를 맵뷰에 등록해야함(register).

4. MKMapViewDelegate의 메서드에서 맵뷰에 등록한 CustomAnnotationView를 dequeue해서 리턴해주면 됨.

MapViewController 전체 코드
import UIKit
import MapKit
final class MapViewController: UIViewController {
typealias Coordinate = (Double, Double)
// MARK: - UIComponents
private lazy var mapView: MKMapView = {
let mapView = MKMapView()
mapView.delegate = self
return mapView
}()
private lazy var locationButton: UIButton = {
let button = UIButton()
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.imageEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8)
button.setImage(UIImage(systemName: "location.circle"), for: .normal)
button.tintColor = .systemMint
button.addTarget(self, action: #selector(didLocationButtonTouched), for: .touchUpInside)
return button
}()
private lazy var rightBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(
title: "Filter",
style: .plain,
target: self,
action: #selector(didFilterButtonTouched)
)
return barButtonItem
}()
private lazy var filterAlert: UIAlertController = {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let action1 = UIAlertAction(title: "메가박스", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = megaboxList
}
let action2 = UIAlertAction(title: "롯데시네마", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = lotteList
}
let action3 = UIAlertAction(title: "CGV", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = cgvList
}
let action4 = UIAlertAction(title: "전체보기", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = megaboxList + lotteList + cgvList
}
let action5 = UIAlertAction(title: "취소", style: .cancel)
[action1, action2, action3, action4, action5].forEach {
alert.addAction($0)
}
return alert
}()
// MARK: - Properties
private let cgvList = [
((37.557908, 126.925766), "CGV 연남"),
((37.530161, 126.965179), "CGV 용산아이파크몰"),
((37.563463, 126.982927), "CGV 명동"),
((37.571073, 126.991204), "CGV 피카디리1958"),
((37.568796, 127.007675), "CGV 을지로"),
((37.583461, 126.999842), "CGV 대학로"),
((37.524357, 127.029423), "CGV 압구정신관"),
((37.501670, 127.026383), "CGV 강남")
]
private let lotteList = [
((37.564191, 126.981633), "롯데시네마 을지로"),
((37.532800, 126.959707), "롯데시네마 용산"),
((37.557297, 126.925099), "롯데시네마 홍대입구"),
((37.551270, 126.913434), "롯데시네마 합정"),
((37.516190, 126.908323), "롯데시네마 영등포"),
((37.516608, 127.021758), "롯데시네마 신사"),
((37.487417, 127.046950), "롯데시네마 도곡"),
((37.513803, 127.104056), "롯데시네마 월드타워"),
((37.538633, 127.073253), "롯데시네마 건대입구")
]
private let megaboxList = [
((37.555991, 126.922044), "메가박스 홍대입구"),
((37.559769, 126.941903), "메가박스 신촌"),
((37.566741, 127.007481), "메가박스 DDP"),
((37.505081, 127.004037), "메가박스 센트럴시티"),
((37.498125, 127.026565), "메가박스 강남"),
((37.541994, 127.044672), "메가박스 성수"),
((37.512909, 127.058698), "메가박스 코엑스"),
((37.555770, 127.078391), "메가박스 군자"),
((37.526939, 126.874437), "메가박스 목동")
]
private var showCinemaList: [(Coordinate, String)] = [] {
didSet {
configureAnnotation()
}
}
override func viewDidLoad() {
super.viewDidLoad()
LocationManager.shared.delegate = self
showCinemaList = cgvList + lotteList + megaboxList
configureUI()
}
@objc func didLocationButtonTouched(_ sender: UIButton) {
LocationManager.shared.checkDeviceLocationAuthorization()
configureMapView()
}
@objc func didFilterButtonTouched(_ sender: UIBarButtonItem) {
present(filterAlert, animated: true)
}
}
// MARK: - Private Methods
private extension MapViewController {
func configureUI() {
configureNavigationBar()
configureLayout()
configureMapView()
}
func configureNavigationBar() {
navigationController?.navigationBar.tintColor = .systemMint
navigationItem.title = "주변의 영화관"
navigationItem.rightBarButtonItem = rightBarButtonItem
}
func configureLayout() {
[
mapView, locationButton
].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate(
[
mapView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mapView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
locationButton.widthAnchor.constraint(equalToConstant: 50),
locationButton.heightAnchor.constraint(equalTo: locationButton.widthAnchor),
locationButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
locationButton.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -20
)
]
)
}
func configureMapView() {
if let coordinate = LocationManager.shared.currenCoordinate {
let region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 5000,
longitudinalMeters: 5000
)
mapView.setRegion(region, animated: true)
}
}
func configureAnnotation() {
mapView.removeAnnotations(mapView.annotations)
showCinemaList.forEach {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: $0.0.0, longitude: $0.0.1)
annotation.title = $0.1
mapView.addAnnotation(annotation)
}
}
}
// MARK: - MKMapViewDelegate 구현부
extension MapViewController: MKMapViewDelegate {
// 지도를 움직일때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(#function)
}
// 지도의 핀이나 화면을 클릭할 때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
print(#function)
}
// 핀의 이미지를 커스텀하게 구성할때 사용하는 메서드
// 1) Custom MKAnnotationView를 구현
// 2) MKMapView 인스턴스에 register
// 3) 테이블뷰처럼 dequeue해서 사용
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// print(#function)
// }
}
// MARK: - LocationManagerDelegate 구현부
extension MapViewController: LocationManagerDelegate {
func presentAuthorizationAlert(alert: UIAlertController) {
present(alert, animated: true)
}
}
unknown과 frozen(feat.Enum)
@unknown (unfrozen Enumeration)
- 새로 case가 추가될 경우 switch문에서 그냥 default로 예외 케이스를 처리하면 warning이 발생하지 않음.
- @unknown default로 처리할 경우, 새로 case가 추가되면 warning으로 이를 알려줌.
let size = UIUserInterfaceSizeClass.compact
switch size {
case .unspecified:
case .compact:
case .regular:
@unknown default:
}
@frozen
- 더 이상 열거형에 추가될 케이스가 없다고 확신할때 enum 키워드 앞에 @frozen 추가하면 됨.
- 해당 열거형은 더이상 케이스의 추가를 고려할 필요가 없음 ➡ 컴파일러는 더욱 효율적으로 컴파일한다.
- Swift의 옵셔널은 frozen enumeration!

'SeSAC' 카테고리의 다른 글
8.24 ~ 8.25 TIL(SeSAC iOS 3기) (0) | 2023.08.28 |
---|---|
8.23 TIL(SeSAC iOS 3기), CoreLocation (0) | 2023.08.27 |
8.21 ~ 8.22 TIL(SeSAC iOS 3기) (0) | 2023.08.27 |
8.14 ~ 8.18 TIL (1) | 2023.08.22 |
8.7 ~ 8.11 TIL (0) | 2023.08.15 |
MapKit
MKMapView를 활용한 맵뷰 구현하기
1. import MapKit
2. MKMapView 인스턴스 생성
3. MKMapView의 region과 annotation 설정
- center: 맵뷰의 중심이 되는 좌표.
- latitudinalMeters, longitudinalMeters: 축적.
- removeAnnotations([Annotation]): 맵뷰의 모든 Annotation을 삭제.
extension MapViewController {
func configureMapView() {
if let coordinate = LocationManager.shared.currenCoordinate {
let region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 5000,
longitudinalMeters: 5000
)
mapView.setRegion(region, animated: true)
}
}
func configureAnnotation() {
mapView.removeAnnotations(mapView.annotations)
showCinemaList.forEach {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: $0.0.0, longitude: $0.0.1)
annotation.title = $0.1
mapView.addAnnotation(annotation)
}
}
}
4. MKMapViewDelegate 구현
extension MapViewController: MKMapViewDelegate {
// 지도를 움직일때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(#function)
}
// 지도의 핀이나 화면을 클릭할 때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
print(#function)
}
// 핀의 이미지를 커스텀하게 구성할때 사용하는 메서드
// 1) Custom MKAnnotationView를 구현
// 2) MKMapView 인스턴스에 register
// 3) 테이블뷰처럼 dequeue해서 사용
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// print(#function)
// }
}
5. MKMapView 인스턴스의 delegate 연결
Custom Annotation 구현 방법
1. MKMapViewDelegate의 mapView(_ mapView:, viewFor annotation:) -> MKAnnotationView? 활용.
2. 위에서 MKAnnotationView를 반환하므로, 이를 상속한 CustomAnnotationView를 구현.
3. 구현한 CustomAnnotationView를 맵뷰에 등록해야함(register).

4. MKMapViewDelegate의 메서드에서 맵뷰에 등록한 CustomAnnotationView를 dequeue해서 리턴해주면 됨.

MapViewController 전체 코드
import UIKit
import MapKit
final class MapViewController: UIViewController {
typealias Coordinate = (Double, Double)
// MARK: - UIComponents
private lazy var mapView: MKMapView = {
let mapView = MKMapView()
mapView.delegate = self
return mapView
}()
private lazy var locationButton: UIButton = {
let button = UIButton()
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.imageEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8)
button.setImage(UIImage(systemName: "location.circle"), for: .normal)
button.tintColor = .systemMint
button.addTarget(self, action: #selector(didLocationButtonTouched), for: .touchUpInside)
return button
}()
private lazy var rightBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(
title: "Filter",
style: .plain,
target: self,
action: #selector(didFilterButtonTouched)
)
return barButtonItem
}()
private lazy var filterAlert: UIAlertController = {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let action1 = UIAlertAction(title: "메가박스", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = megaboxList
}
let action2 = UIAlertAction(title: "롯데시네마", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = lotteList
}
let action3 = UIAlertAction(title: "CGV", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = cgvList
}
let action4 = UIAlertAction(title: "전체보기", style: .default) { [weak self] _ in
guard let self else { return }
showCinemaList = megaboxList + lotteList + cgvList
}
let action5 = UIAlertAction(title: "취소", style: .cancel)
[action1, action2, action3, action4, action5].forEach {
alert.addAction($0)
}
return alert
}()
// MARK: - Properties
private let cgvList = [
((37.557908, 126.925766), "CGV 연남"),
((37.530161, 126.965179), "CGV 용산아이파크몰"),
((37.563463, 126.982927), "CGV 명동"),
((37.571073, 126.991204), "CGV 피카디리1958"),
((37.568796, 127.007675), "CGV 을지로"),
((37.583461, 126.999842), "CGV 대학로"),
((37.524357, 127.029423), "CGV 압구정신관"),
((37.501670, 127.026383), "CGV 강남")
]
private let lotteList = [
((37.564191, 126.981633), "롯데시네마 을지로"),
((37.532800, 126.959707), "롯데시네마 용산"),
((37.557297, 126.925099), "롯데시네마 홍대입구"),
((37.551270, 126.913434), "롯데시네마 합정"),
((37.516190, 126.908323), "롯데시네마 영등포"),
((37.516608, 127.021758), "롯데시네마 신사"),
((37.487417, 127.046950), "롯데시네마 도곡"),
((37.513803, 127.104056), "롯데시네마 월드타워"),
((37.538633, 127.073253), "롯데시네마 건대입구")
]
private let megaboxList = [
((37.555991, 126.922044), "메가박스 홍대입구"),
((37.559769, 126.941903), "메가박스 신촌"),
((37.566741, 127.007481), "메가박스 DDP"),
((37.505081, 127.004037), "메가박스 센트럴시티"),
((37.498125, 127.026565), "메가박스 강남"),
((37.541994, 127.044672), "메가박스 성수"),
((37.512909, 127.058698), "메가박스 코엑스"),
((37.555770, 127.078391), "메가박스 군자"),
((37.526939, 126.874437), "메가박스 목동")
]
private var showCinemaList: [(Coordinate, String)] = [] {
didSet {
configureAnnotation()
}
}
override func viewDidLoad() {
super.viewDidLoad()
LocationManager.shared.delegate = self
showCinemaList = cgvList + lotteList + megaboxList
configureUI()
}
@objc func didLocationButtonTouched(_ sender: UIButton) {
LocationManager.shared.checkDeviceLocationAuthorization()
configureMapView()
}
@objc func didFilterButtonTouched(_ sender: UIBarButtonItem) {
present(filterAlert, animated: true)
}
}
// MARK: - Private Methods
private extension MapViewController {
func configureUI() {
configureNavigationBar()
configureLayout()
configureMapView()
}
func configureNavigationBar() {
navigationController?.navigationBar.tintColor = .systemMint
navigationItem.title = "주변의 영화관"
navigationItem.rightBarButtonItem = rightBarButtonItem
}
func configureLayout() {
[
mapView, locationButton
].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate(
[
mapView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mapView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
locationButton.widthAnchor.constraint(equalToConstant: 50),
locationButton.heightAnchor.constraint(equalTo: locationButton.widthAnchor),
locationButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
locationButton.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -20
)
]
)
}
func configureMapView() {
if let coordinate = LocationManager.shared.currenCoordinate {
let region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 5000,
longitudinalMeters: 5000
)
mapView.setRegion(region, animated: true)
}
}
func configureAnnotation() {
mapView.removeAnnotations(mapView.annotations)
showCinemaList.forEach {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: $0.0.0, longitude: $0.0.1)
annotation.title = $0.1
mapView.addAnnotation(annotation)
}
}
}
// MARK: - MKMapViewDelegate 구현부
extension MapViewController: MKMapViewDelegate {
// 지도를 움직일때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(#function)
}
// 지도의 핀이나 화면을 클릭할 때마다 호출되는 메서드
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
print(#function)
}
// 핀의 이미지를 커스텀하게 구성할때 사용하는 메서드
// 1) Custom MKAnnotationView를 구현
// 2) MKMapView 인스턴스에 register
// 3) 테이블뷰처럼 dequeue해서 사용
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// print(#function)
// }
}
// MARK: - LocationManagerDelegate 구현부
extension MapViewController: LocationManagerDelegate {
func presentAuthorizationAlert(alert: UIAlertController) {
present(alert, animated: true)
}
}
unknown과 frozen(feat.Enum)
@unknown (unfrozen Enumeration)
- 새로 case가 추가될 경우 switch문에서 그냥 default로 예외 케이스를 처리하면 warning이 발생하지 않음.
- @unknown default로 처리할 경우, 새로 case가 추가되면 warning으로 이를 알려줌.
let size = UIUserInterfaceSizeClass.compact
switch size {
case .unspecified:
case .compact:
case .regular:
@unknown default:
}
@frozen
- 더 이상 열거형에 추가될 케이스가 없다고 확신할때 enum 키워드 앞에 @frozen 추가하면 됨.
- 해당 열거형은 더이상 케이스의 추가를 고려할 필요가 없음 ➡ 컴파일러는 더욱 효율적으로 컴파일한다.
- Swift의 옵셔널은 frozen enumeration!

'SeSAC' 카테고리의 다른 글
8.24 ~ 8.25 TIL(SeSAC iOS 3기) (0) | 2023.08.28 |
---|---|
8.23 TIL(SeSAC iOS 3기), CoreLocation (0) | 2023.08.27 |
8.21 ~ 8.22 TIL(SeSAC iOS 3기) (0) | 2023.08.27 |
8.14 ~ 8.18 TIL (1) | 2023.08.22 |
8.7 ~ 8.11 TIL (0) | 2023.08.15 |