Interaktive Kartenübersicht
Beschreibung
Diese View zeigt eine interaktive Karte an, auf der Nutzer verschiedene Bereiche erkunden können. Durch Antippen von Standorten erhalten sie detaillierte Informationen; benutzerfreundliche Icons und Tooltips erleichtern die Interaktion und sorgen für Übersichtlichkeit.
🔍 Zweck
- Stadtführer mit Sehenswürdigkeiten zum Antippen anzeigen
- Event-Locations auf einer Messekarte präsentieren
- Gastronomische Angebote auf einer Stadtkarte darstellen
- Immobilienstandorte auf einer Karte visualisieren und auswählen lassen
- Naturparks mit interaktiven Informationstafeln abbilden
📄 Codebeispiel
import SwiftUI
import MapKit
struct MapLocationPoint: Identifiable, Equatable {
// Use name and coordinate hash to create a stable, predictable ID for static demo data.
let id: String
let name: String
let coordinate: CLLocationCoordinate2D
let iconName: String // SF Symbol for pin/icon display
let description: String?
init(name: String, coordinate: CLLocationCoordinate2D, iconName: String, description: String? = nil) {
self.name = name
self.coordinate = coordinate
self.iconName = iconName
self.description = description
// Generate stable id from name and coordinates to prevent multiple UUIDs for same data
self.id = "\(name)_\(coordinate.latitude)_\(coordinate.longitude)"
}
static func == (lhs: MapLocationPoint, rhs: MapLocationPoint) -> Bool {
// Compare all properties that define equality
lhs.id == rhs.id
}
}
// MARK: - Demo data
let demoLocations: [MapLocationPoint] = [
MapLocationPoint(
name: "Brandenburger Tor",
coordinate: CLLocationCoordinate2D(latitude: 52.5163, longitude: 13.3777),
iconName: "building.columns.fill",
description: "Berühmtes Wahrzeichen Berlins"
),
MapLocationPoint(
name: "Museumsinsel",
coordinate: CLLocationCoordinate2D(latitude: 52.5169, longitude: 13.4019),
iconName: "paintpalette.fill",
description: "Zahlreiche renommierte Museen"
),
MapLocationPoint(
name: "Tiergarten",
coordinate: CLLocationCoordinate2D(latitude: 52.5145, longitude: 13.3501),
iconName: "leaf.fill",
description: "Grüne Oase mitten in der Stadt"
),
]
struct ContentView: View {
@State private var mapRegion: MKCoordinateRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 52.5170, longitude: 13.3889),
span: MKCoordinateSpan(latitudeDelta: 0.04, longitudeDelta: 0.09)
)
@State private var selectedLocation: MapLocationPoint? = nil
var body: some View {
ZStack(alignment: .topLeading) {
// Main interactive map with annotation items
Map(coordinateRegion: $mapRegion,
annotationItems: demoLocations
) { location in
// Use MapAnnotation for custom marker with tap interaction.
MapAnnotation(coordinate: location.coordinate) {
Button {
// On tap, set as selected (shows info sheet)
selectedLocation = location
} label: {
VStack(spacing: -2) {
Image(systemName: location.iconName)
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(selectedLocation == location ? Color.accentColor : Color.blue)
// Show glow effect if selected
.shadow(
color: selectedLocation == location ? Color.accentColor.opacity(0.5) : .clear,
radius: selectedLocation == location ? 7 : 0
)
// Tooltip-style label below the icon
ZStack {
Capsule()
.fill(Color.white.opacity(0.95))
.shadow(radius: 2)
Text(location.name)
.font(.caption2)
.bold()
.foregroundColor(.black)
.padding(.horizontal, 7)
.padding(.vertical, 1)
}
.fixedSize()
.opacity(selectedLocation != location ? 1 : 0)
// Small pointer triangle
TrianglePointer()
.fill(Color.white.opacity(0.95))
.frame(width: 12, height: 6)
.offset(y: -4)
.opacity(selectedLocation != location ? 1 : 0)
}
.animation(.spring(duration: 0.2), value: selectedLocation)
}
.buttonStyle(.plain)
.accessibilityLabel(Text(location.name))
}
}
.ignoresSafeArea() // Use current API
// Overlay info bar
HStack {
Image(systemName: "map.fill")
.foregroundColor(.accentColor)
Text("Interaktive Karte – tippe auf Symbole!")
.fontWeight(.medium)
.foregroundColor(.primary)
}
.padding(12)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(Color.white.opacity(0.9))
.shadow(radius: 3)
)
.padding([.top, .leading], 20)
}
// Sheet with detailed info for selected marker/location
.sheet(item: $selectedLocation) { loc in
VStack(spacing: 12) {
Image(systemName: loc.iconName)
.resizable()
.scaledToFit()
.frame(width: 55, height: 55)
.foregroundColor(Color.accentColor)
.shadow(radius: 6)
Text(loc.name)
.font(.title3)
.bold()
if let desc = loc.description {
Text(desc)
.multilineTextAlignment(.center)
.font(.body)
.foregroundColor(.secondary)
}
Button(role: .cancel) {
selectedLocation = nil
} label: {
Label("Schließen", systemImage: "xmark.circle")
}
}
.padding()
.presentationDetents([.height(230)])
}
}
}
// MARK: - Tooltip triangle pointer for marker label
struct TrianglePointer: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
// Draws a simple triangle pointer
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width / 2, y: rect.height))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.closeSubpath()
return path
}
}
#Preview {
NavigationStack {
ContentView()
}
}