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
- SwiftUI Map View (iOS 17+)
- CLLocationManager ile Konum
- Annotations ve Markers
- Overlays ve Polylines
- Geocoding ve Reverse Geocoding
- Geofencing
- Directions API
- Custom Map Styles
- Look Around Preview
- Performance ve Best Practices
SwiftUI Map View (iOS 17+) {#swiftui-map}
swift
1import MapKit2import SwiftUI3 4struct MapContentView: View {5 @State private var position: MapCameraPosition = .automatic6 @State private var selectedPlace: Place?7 8 var body: some View {9 Map(position: $position, selection: $selectedPlace) {10 // Markers11 ForEach(places) { place in12 Marker(place.name, coordinate: place.coordinate)13 .tint(place.category.color)14 .tag(place)15 }16 17 // User location18 UserAnnotation()19 20 // Custom annotation21 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 overlay32 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 = .notDetermined5 6 override init() {7 super.init()8 manager.delegate = self9 manager.desiredAccuracy = kCLLocationAccuracyBest10 manager.distanceFilter = 10 // 10 metre değişimde güncelle11 }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.last23 }24 25 func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {26 authorizationStatus = manager.authorizationStatus27 }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 marker2Marker("Restoran", systemImage: "fork.knife", coordinate: coordinate)3 .tint(.orange)4 5// Custom annotation view6Annotation("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 çizgisi2MapPolyline(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// Polygon11MapPolygon(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 → Koordinat4func 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.notFound8 }9 return location.coordinate10}11 12// Koordinat → Adres13func 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.notFound18 }19 return [placemark.thoroughfare, placemark.locality, placemark.country]20 .compactMap { $0 }21 .joined(separator: ", ")22}Geofencing {#geofencing}
swift
1// Geofence region oluştur2func monitorStore(location: CLLocationCoordinate2D, identifier: String) {3 let region = CLCircularRegion(4 center: location,5 radius: 200, // 200 metre6 identifier: identifier7 )8 region.notifyOnEntry = true9 region.notifyOnExit = true10 manager.startMonitoring(for: region)11}12 13// Delegate callback14func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {15 // Kullanıcı bölgeye girdi - hoşgeldin notification gönder16 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 = .automobile6 7 let directions = MKDirections(request: request)8 let response = try await directions.calculate()9 guard let route = response.routes.first else {10 throw DirectionsError.noRoute11 }12 return route // route.distance, route.expectedTravelTime, route.polyline13}Custom Map Styles {#custom-styles}
swift
1// iOS 17+ Map Styles2.mapStyle(.standard) // Standart3.mapStyle(.standard(elevation: .realistic)) // 3D binalar4.mapStyle(.imagery) // Uydu görüntüsü5.mapStyle(.hybrid) // Hibrit6.mapStyle(.standard(pointsOfInterest: .including([.restaurant, .cafe]))) // Filtreli POILook 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.scene15 }16}Performance ve Best Practices {#best-practices}
- Minimum accuracy iste —
kCLLocationAccuracyHundredMetersçoğu uygulama için yeterli - Distance filter kullan — gereksiz güncelleme önle
- Significant location changes — arka plan için enerji-verimli
- Geofence limiti — uygulama başına maksimum 20 region
- 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 Annotation2struct ClusteredMapView: View {3 let locations: [StoreLocation]4 @State private var position: MapCameraPosition = .automatic5 6 var body: some View {7 Map(position: $position) {8 ForEach(locations) { location in9 Marker(location.name, coordinate: location.coordinate)10 .tint(location.isOpen ? .green : .red)11 .annotationTitles(.hidden) // Zoom out'ta gizle12 }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 = .systemBlue24 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.count33 image = drawClusterImage(count: count)34 displayPriority = .defaultHigh35 }36 }37 38 private func drawClusterImage(count: Int) -> UIImage {39 let renderer = UIGraphicsImageRenderer(size: CGSize(width: 44, height: 44))40 return renderer.image { ctx in41 UIColor.systemBlue.setFill()42 ctx.cgContext.fillEllipse(in: CGRect(x: 0, y: 0, width: 44, height: 44))43 let text = "\(count)" as NSString44 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 = size9 options.mapType = .standard10 options.showsBuildings = true11 12 let snapshotter = MKMapSnapshotter(options: options)13 let snapshot = try await snapshotter.start()14 15 // Snapshot üzerine pin çiz16 let renderer = UIGraphicsImageRenderer(size: size)17 return renderer.image { ctx in18 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ım27struct MapWidgetView: View {28 let snapshot: UIImage29 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)

