Skip to main content

mapkit-performance-issue | ios-app-dev-memo

  • 距離が近いものをスキップする
  • 画面表示範囲される範囲のみ表示
swift
import SwiftUI

import MapKit

struct ContentView: View {
@State var parks: [Park] = []
// @State var limits: [Int] = [100, 1000, 5000, 10000, 100000]
@State var limit: Int = 100000

@State var region: MKCoordinateRegion? = nil

var body: some View {
VStack {
Map {
ForEach(parks) { park in
Annotation(park.name, coordinate: park.coordinate) {
Circle()
.foregroundStyle(.red)
}
}
}
.onMapCameraChange { context in
loadParks(limit: limit, context: context)
}
/*
HStack {
Spacer()
ForEach(limits, id: \.self) { limit in
Button("Load \(limit)") {
loadParks(limit: limit)
}
Spacer()
}
}
.padding()
*/
}
.padding()
}

func loadParks(limit: Int, context: MapCameraUpdateContext? = nil) {
self.limit = limit

let manager = FileManager.default
guard let path = Bundle.main.path(forResource: "parks", ofType: "csv"),
let content = manager.contents(atPath: path),
let text = String(data: content, encoding: .utf8)
else {
return
}
let csvRows = text.split(separator: "\n").map({ $0.components(separatedBy: ",") }).dropFirst()

if let context = context {
if regionMatches(region, context.region) {
return
}
region = context.region
}

var allParks = csvRows.map({ Park(csvRow: $0) })

print("parks (all): \(allParks.count)")

if let region = region {
let tmpParks = allParks.filter({ $0.isIn(region: region) })
print("parks (in region): \(tmpParks.count)")

let gridWidth = min(region.span.latitudeDelta, region.span.longitudeDelta) / 25

allParks = []
var gridCoords: Set<[Double]> = []
for park in tmpParks {
let gridCoord = park.gridCoordinateD(for: gridWidth)
if gridCoords.contains(gridCoord) {
continue
}
allParks.append(park)
gridCoords.insert(gridCoord)
}
print("parks (after culling): \(allParks.count)")
}

print("parks: \(allParks.count)")

withAnimation {
if allParks.count > limit {
parks = Array(allParks[0..<limit])
} else {
parks = allParks
}
}
print("loaded")
}

func regionMatches(_ lhs: MKCoordinateRegion?, _ rhs: MKCoordinateRegion?) -> Bool {
guard let lhs = lhs,
let rhs = rhs
else {
return false
}
print(lhs)
print(rhs)
print("------------------------------------------------------------")
let minDiff = 0.1
if abs(lhs.center.latitude - rhs.center.latitude) > minDiff {
return false
}
if abs(lhs.center.longitude - rhs.center.longitude) > minDiff {
return false
}
if abs(lhs.span.latitudeDelta - rhs.span.latitudeDelta) > minDiff {
return false
}
if abs(lhs.span.longitudeDelta - rhs.span.longitudeDelta) > minDiff {
return false
}
return true
}
}
swift

import MapKit

struct Park: Identifiable {
var id : UUID = UUID()

var name : String
var prefecture : String
var city : String
var latitude : Double
var longitude : Double

init(csvRow: [String]) {
name = csvRow[0]
prefecture = csvRow[1]
city = csvRow[2]
latitude = Double(csvRow[3])!
longitude = Double(csvRow[4])!
}

var coordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: latitude,
longitude: longitude
)
}

func gridCoordinateD(for width: Double) -> [Double] {
return [
Double(Int(latitude / width)) * width,
Double(Int(longitude / width)) * width,
]
}
func gridCoordinate(for width: Double) -> CLLocationCoordinate2D {
let coord = gridCoordinateD(for: width)
return CLLocationCoordinate2D(
latitude: coord[0],
longitude: coord[1]
)
}

func isIn(region: MKCoordinateRegion) -> Bool {
let scale = 1.5
let latStart = region.center.latitude - (region.span.latitudeDelta / 2) * scale
let latEnd = region.center.latitude + (region.span.latitudeDelta / 2) * scale
let longStart = region.center.longitude - (region.span.longitudeDelta / 2) * scale
let longEnd = region.center.longitude + (region.span.longitudeDelta / 2) * scale

return latStart < latitude && latitude < latEnd
&& longStart < longitude && longitude < longEnd
}
}