Tüm Yazılar
KategoriiOS
Okuma Süresi
20 dk
Yayın Tarihi
...
Kelime Sayısı
1.497kelime

Kahveni hazırla - bu içerikli bir makale!

MapKit ile SwiftUI harita uygulamaları, CLLocationManager, geofencing, annotations, overlays ve iOS 17+ yeni MapKit API'ları.

MapKit ve Location Services: Harita Uygulamaları Rehberi

Harita uygulamaları, mobil platformların en güçlü kullanım alanlarından biri. Uber, Yemeksepeti, Trendyol — hepsi MapKit ve Location Services kullanıyor. iOS 17 ile MapKit tamamen yenilendi ve SwiftUI-native API'lar geldi.

💡 Hızlı Not: iOS 17+, MapKit'te devrim niteliğinde değişiklikler getirdi. Bu rehber hem iOS 16 (MKMapView) hem iOS 17+ (Map SwiftUI) API'larını kapsar.

İçindekiler

  1. SwiftUI Map View (iOS 17+)
  2. CLLocationManager ile Konum
  3. Annotations ve Markers
  4. Overlays ve Polylines
  5. Geocoding ve Reverse Geocoding
  6. Geofencing
  7. Directions API
  8. Custom Map Styles
  9. Look Around Preview
  10. Performance ve Best Practices

SwiftUI Map View (iOS 17+) {#swiftui-map}

swift
1import MapKit
2import SwiftUI
3 
4struct MapContentView: View {
5 @State private var position: MapCameraPosition = .automatic
6 @State private var selectedPlace: Place?
7 
8 var body: some View {
9 Map(position: $position, selection: $selectedPlace) {
10 // Markers
11 ForEach(places) { place in
12 Marker(place.name, coordinate: place.coordinate)
13 .tint(place.category.color)
14 .tag(place)
15 }
16 
17 // User location
18 UserAnnotation()
19 
20 // Custom annotation
21 Annotation("Ofis", coordinate: officeLocation) {
22 ZStack {
23 Circle()
24 .fill(.blue.opacity(0.3))
25 .frame(width: 40, height: 40)
26 Image(systemName: "building.2")
27 .foregroundStyle(.blue)
28 }
29 }
30 
31 // Route overlay
32 if let route {
33 MapPolyline(route.polyline)
34 .stroke(.blue, lineWidth: 5)
35 }
36 }
37 .mapStyle(.standard(elevation: .realistic))
38 .mapControls {
39 MapCompass()
40 MapUserLocationButton()
41 MapScaleView()
42 }
43 }
44}

CLLocationManager ile Konum {#location-manager}

swift
1class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
2 private let manager = CLLocationManager()
3 @Published var location: CLLocation?
4 @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
5 
6 override init() {
7 super.init()
8 manager.delegate = self
9 manager.desiredAccuracy = kCLLocationAccuracyBest
10 manager.distanceFilter = 10 // 10 metre değişimde güncelle
11 }
12 
13 func requestPermission() {
14 manager.requestWhenInUseAuthorization()
15 }
16 
17 func startTracking() {
18 manager.startUpdatingLocation()
19 }
20 
21 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
22 location = locations.last
23 }
24 
25 func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
26 authorizationStatus = manager.authorizationStatus
27 }
28}

Konum İzni Karşılaştırma

İzin
Kullanım
Arka Plan
Hassasiyet
When In Use
Uygulama açıkken
Tam
Always
Her zaman
Tam
Reduced Accuracy
Privacy-friendly
Seçime bağlı
~5 km

Annotations ve Markers {#annotations}

iOS 17+ ile marker'lar çok daha kolay:

swift
1// Basit marker
2Marker("Restoran", systemImage: "fork.knife", coordinate: coordinate)
3 .tint(.orange)
4 
5// Custom annotation view
6Annotation("Etkinlik", coordinate: eventLocation, anchor: .bottom) {
7 VStack(spacing: 4) {
8 Image(systemName: "calendar")
9 .padding(8)
10 .background(.red)
11 .foregroundStyle(.white)
12 .clipShape(Circle())
13 Text("Bugün!")
14 .font(.caption2)
15 .bold()
16 }
17}

Overlays ve Polylines {#overlays}

swift
1// Rota çizgisi
2MapPolyline(route.polyline)
3 .stroke(.blue, lineWidth: 4)
4 
5// Daire overlay (geofence görselleştirme)
6MapCircle(center: storeLocation, radius: 500)
7 .foregroundStyle(.blue.opacity(0.2))
8 .stroke(.blue, lineWidth: 2)
9 
10// Polygon
11MapPolygon(coordinates: regionBoundary)
12 .foregroundStyle(.green.opacity(0.1))
13 .stroke(.green, lineWidth: 1)

Geocoding ve Reverse Geocoding {#geocoding}

swift
1let geocoder = CLGeocoder()
2 
3// Adres → Koordinat
4func geocode(address: String) async throws -> CLLocationCoordinate2D {
5 let placemarks = try await geocoder.geocodeAddressString(address)
6 guard let location = placemarks.first?.location else {
7 throw GeocodingError.notFound
8 }
9 return location.coordinate
10}
11 
12// Koordinat → Adres
13func reverseGeocode(coordinate: CLLocationCoordinate2D) async throws -> String {
14 let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
15 let placemarks = try await geocoder.reverseGeocodeLocation(location)
16 guard let placemark = placemarks.first else {
17 throw GeocodingError.notFound
18 }
19 return [placemark.thoroughfare, placemark.locality, placemark.country]
20 .compactMap { $0 }
21 .joined(separator: ", ")
22}

Geofencing {#geofencing}

swift
1// Geofence region oluştur
2func monitorStore(location: CLLocationCoordinate2D, identifier: String) {
3 let region = CLCircularRegion(
4 center: location,
5 radius: 200, // 200 metre
6 identifier: identifier
7 )
8 region.notifyOnEntry = true
9 region.notifyOnExit = true
10 manager.startMonitoring(for: region)
11}
12 
13// Delegate callback
14func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
15 // Kullanıcı bölgeye girdi - hoşgeldin notification gönder
16 sendLocalNotification(title: "Hoş geldiniz!", body: "\(region.identifier) mağazasına yakınsınız.")
17}

Directions API {#directions}

swift
1func getRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) async throws -> MKRoute {
2 let request = MKDirections.Request()
3 request.source = MKMapItem(placemark: MKPlacemark(coordinate: source))
4 request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination))
5 request.transportType = .automobile
6 
7 let directions = MKDirections(request: request)
8 let response = try await directions.calculate()
9 guard let route = response.routes.first else {
10 throw DirectionsError.noRoute
11 }
12 return route // route.distance, route.expectedTravelTime, route.polyline
13}

Custom Map Styles {#custom-styles}

swift
1// iOS 17+ Map Styles
2.mapStyle(.standard) // Standart
3.mapStyle(.standard(elevation: .realistic)) // 3D binalar
4.mapStyle(.imagery) // Uydu görüntüsü
5.mapStyle(.hybrid) // Hibrit
6.mapStyle(.standard(pointsOfInterest: .including([.restaurant, .cafe]))) // Filtreli POI

Look Around Preview {#look-around}

swift
1struct LookAroundView: View {
2 @State private var lookAroundScene: MKLookAroundScene?
3 
4 var body: some View {
5 if let scene = lookAroundScene {
6 LookAroundPreview(scene: scene)
7 .frame(height: 300)
8 .clipShape(RoundedRectangle(cornerRadius: 12))
9 }
10 }
11 
12 func loadScene(for coordinate: CLLocationCoordinate2D) async {
13 let request = MKLookAroundSceneRequest(coordinate: coordinate)
14 lookAroundScene = try? await request.scene
15 }
16}

Performance ve Best Practices {#best-practices}

  1. Minimum accuracy iste — kCLLocationAccuracyHundredMeters çoğu uygulama için yeterli
  2. Distance filter kullan — gereksiz güncelleme önle
  3. Significant location changes — arka plan için enerji-verimli
  4. Geofence limiti — uygulama başına maksimum 20 region
  5. Always izni — gerçekten gerekli değilse isteme

Cluster Annotations {#cluster-annotations}

Binlerce marker'ı performanslı şekilde göstermek için cluster annotation kullan:

swift
1// iOS 17+ Cluster Annotation
2struct ClusteredMapView: View {
3 let locations: [StoreLocation]
4 @State private var position: MapCameraPosition = .automatic
5 
6 var body: some View {
7 Map(position: $position) {
8 ForEach(locations) { location in
9 Marker(location.name, coordinate: location.coordinate)
10 .tint(location.isOpen ? .green : .red)
11 .annotationTitles(.hidden) // Zoom out'ta gizle
12 }
13 }
14 .mapStyle(.standard(pointsOfInterest: .excludingAll))
15 }
16}
17 
18// UIKit ile MKClusterAnnotation (iOS 16 uyumlu)
19class StoreAnnotationView: MKMarkerAnnotationView {
20 override func prepareForDisplay() {
21 super.prepareForDisplay()
22 clusteringIdentifier = "stores"
23 markerTintColor = .systemBlue
24 glyphImage = UIImage(systemName: "storefront")
25 }
26}
27 
28class StoreClusterView: MKAnnotationView {
29 override func prepareForDisplay() {
30 super.prepareForDisplay()
31 if let cluster = annotation as? MKClusterAnnotation {
32 let count = cluster.memberAnnotations.count
33 image = drawClusterImage(count: count)
34 displayPriority = .defaultHigh
35 }
36 }
37 
38 private func drawClusterImage(count: Int) -> UIImage {
39 let renderer = UIGraphicsImageRenderer(size: CGSize(width: 44, height: 44))
40 return renderer.image { ctx in
41 UIColor.systemBlue.setFill()
42 ctx.cgContext.fillEllipse(in: CGRect(x: 0, y: 0, width: 44, height: 44))
43 let text = "\(count)" as NSString
44 let attrs: [NSAttributedString.Key: Any] = [
45 .foregroundColor: UIColor.white,
46 .font: UIFont.boldSystemFont(ofSize: 16)
47 ]
48 let size = text.size(withAttributes: attrs)
49 text.draw(at: CGPoint(x: 22 - size.width/2, y: 22 - size.height/2), withAttributes: attrs)
50 }
51 }
52}

Cluster vs Bireysel Marker Performans

Marker Sayısı
Cluster Yok (FPS)
Cluster Var (FPS)
Bellek
100
60
60
~2 MB
1.000
45
60
~8 MB vs ~3 MB
10.000
15
58
~40 MB vs ~5 MB
50.000
3
55
Crash riski vs ~8 MB

Map Snapshots {#map-snapshots}

Haritanın statik görüntüsünü almak için MKMapSnapshotter kullan — paylaşım, bildirim ve widget'larda kullanışlı:

swift
1func takeMapSnapshot(
2 center: CLLocationCoordinate2D,
3 span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01),
4 size: CGSize = CGSize(width: 300, height: 200)
5) async throws -> UIImage {
6 let options = MKMapSnapshotter.Options()
7 options.region = MKCoordinateRegion(center: center, span: span)
8 options.size = size
9 options.mapType = .standard
10 options.showsBuildings = true
11 
12 let snapshotter = MKMapSnapshotter(options: options)
13 let snapshot = try await snapshotter.start()
14 
15 // Snapshot üzerine pin çiz
16 let renderer = UIGraphicsImageRenderer(size: size)
17 return renderer.image { ctx in
18 snapshot.image.draw(at: .zero)
19 let point = snapshot.point(for: center)
20 let pinImage = UIImage(systemName: "mappin.circle.fill")!
21 .withTintColor(.red, renderingMode: .alwaysOriginal)
22 pinImage.draw(at: CGPoint(x: point.x - 12, y: point.y - 24), blendMode: .normal, alpha: 1.0)
23 }
24}
25 
26// Widget'ta kullanım
27struct MapWidgetView: View {
28 let snapshot: UIImage
29 
30 var body: some View {
31 Image(uiImage: snapshot)
32 .resizable()
33 .aspectRatio(contentMode: .fill)
34 .overlay(alignment: .bottomLeading) {
35 Text("Son konum")
36 .font(.caption2)
37 .padding(4)
38 .background(.ultraThinMaterial)
39 .clipShape(RoundedRectangle(cornerRadius: 4))
40 .padding(8)
41 }
42 }
43}

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Okuyucu Ödülü

Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Apple: MapKit Documentation](https://developer.apple.com/documentation/mapkit) - [WWDC23: Meet MapKit for SwiftUI](https://developer.apple.com/videos/play/wwdc2023/10043/) - [Apple: Core Location](https://developer.apple.com/documentation/corelocation)

Etiketler

#mapkit#location#corelocation#geofencing#maps#ios#swiftui
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz