From ea661f69e801ddddf15f5ade5ceee012b3be9fcd Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Mon, 24 Mar 2025 16:37:20 +0700 Subject: [PATCH 01/20] Update changes --- .../w3w-swift-components-map-apple.xcscheme | 79 ++ Package.resolved | 32 +- Package.swift | 8 +- .../Drawing/Grid/W3WAppleMapGridDrawing.swift | 1033 +++++++++++++++++ .../Helper/W3WAppleMapHelper.swift | 530 +++++++++ .../Helper/W3WAppleMapHelperProtocol.swift | 59 + .../Type/W3WAppleMapAnnotation.swift | 58 + .../Type/W3WAppleMapGridData.swift | 234 ++++ .../Type/W3WAppleMapTypes.swift | 14 + .../Type/W3WAreaMath.swift | 62 + .../Type/W3WImageCache.swift | 34 + .../View/W3WAppleMapView.swift | 358 ++++++ .../W3WAppleMapView.swift | 56 - 13 files changed, 2491 insertions(+), 66 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/w3w-swift-components-map-apple.xcscheme create mode 100644 Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelperProtocol.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapAnnotation.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapTypes.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WAreaMath.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift create mode 100644 Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift delete mode 100644 Sources/W3WSwiftComponentsMapApple/W3WAppleMapView.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/w3w-swift-components-map-apple.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/w3w-swift-components-map-apple.xcscheme new file mode 100644 index 0000000..e283084 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/w3w-swift-components-map-apple.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved index d021fe7..3e074da 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "2d2ff9066c4dc07bb0a0d8e58d69518e67d94b8d4e3545ed8609ccc5deddf020", + "originHash" : "7c1690b067e4e0d940b96b74d150a0946e164fc0e6c3147a12b94185d8604f33", "pins" : [ { - "identity" : "w3w-swift-components-map", + "identity" : "w3w-swift-components", "kind" : "remoteSourceControl", - "location" : "git@github.com:what3words/w3w-swift-components-map.git", + "location" : "https://github.com/what3words/w3w-swift-components.git", "state" : { - "branch" : "main", - "revision" : "c311d5682e05f87a910955a8acc78a2e1104ee67" + "revision" : "6ed04be4011d86642d1f8b9bbab8ac118bedbd86", + "version" : "3.0.0" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-core.git", "state" : { - "revision" : "598fc648928208d9f0268680ce15b686329d12f9", - "version" : "1.1.2" + "revision" : "5e81cb99e3424d0de58e5c3df12716b834a44685", + "version" : "1.1.3" } }, { @@ -36,6 +36,24 @@ "revision" : "718555fc83d5745724a222c6ba39add74e0d345e", "version" : "1.2.2" } + }, + { + "identity" : "w3w-swift-voice-api", + "kind" : "remoteSourceControl", + "location" : "https://github.com/what3words/w3w-swift-voice-api.git", + "state" : { + "revision" : "fa5849ae926e1b5f85198ca3d4d52ae2c9446cd5", + "version" : "1.0.0" + } + }, + { + "identity" : "w3w-swift-wrapper", + "kind" : "remoteSourceControl", + "location" : "https://github.com/what3words/w3w-swift-wrapper", + "state" : { + "branch" : "master", + "revision" : "3c01152aa316b7a9714f038b34a8adb4adeb7c4a" + } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index 7ab0815..fa6c89b 100644 --- a/Package.swift +++ b/Package.swift @@ -15,8 +15,9 @@ let package = Package( dependencies: [ .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), - .package(url: "git@github.com:what3words/w3w-swift-components-map.git", branch: "main"), - .package(url: "https://github.com/what3words/w3w-swift-core.git", "1.0.0"..<"2.0.0") + .package(path: "../w3w-swift-components-map"), + .package(url: "https://github.com/what3words/w3w-swift-core.git", "1.0.0"..<"2.0.0"), + .package(url: "https://github.com/what3words/w3w-swift-wrapper", branch: "master") ], targets: [ @@ -28,7 +29,8 @@ let package = Package( .product(name: "W3WSwiftCore", package: "w3w-swift-core"), .product(name: "W3WSwiftDesign", package: "w3w-swift-design"), .product(name: "W3WSwiftComponentsMap", package: "w3w-swift-components-map"), - .product(name: "W3WSwiftThemes", package: "w3w-swift-themes") + .product(name: "W3WSwiftThemes", package: "w3w-swift-themes"), + .product(name: "W3WSwiftApi", package: "w3w-swift-wrapper") ] ), diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift new file mode 100644 index 0000000..aafbfec --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift @@ -0,0 +1,1033 @@ +// +// W3WApplemapViewProtocol.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 17/1/25. +// + +#if !os(macOS) && !os(watchOS) + +import Foundation +import MapKit +import W3WSwiftCore +import W3WSwiftThemes +import W3WSwiftComponentsMap +import Combine + + +public protocol W3WAppleMapGridDrawingProtocol { + + var mapView: MKMapView? { get } + var region: MKCoordinateRegion { get } + var overlays: [MKOverlay] { get } + var annotations: [MKAnnotation] { get } + var mapGridData: W3WAppleMapGridData? { get set } + + func addOverlay(_ overlay: MKOverlay) + func addOverlay(_ overlay: MKOverlay, _ color: W3WColor?) + func removeOverlay(_ overlay: MKOverlay) + func removeOverlays(_ overlays: [MKOverlay]) + func addAnnotation(_ annotation: MKAnnotation) + func removeAnnotation(_ annotation: MKAnnotation) + + func setRegion(_ region: MKCoordinateRegion, animated: Bool) + func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) +} + +extension W3WAppleMapGridDrawingProtocol { + + public func updateMap() { + + updateGrid() + + if let lastZoomPointsPerSquare = mapGridData?.lastZoomPointsPerSquare { + let squareSize = getPointsPerSquare() + if (squareSize < CGFloat(12.0) && lastZoomPointsPerSquare > CGFloat(12.0)) || (squareSize > CGFloat(12.0) && lastZoomPointsPerSquare < CGFloat(12.0)) { + + redrawPins() + } + + mapGridData?.lastZoomPointsPerSquare = squareSize + } + } + + func updateGrid() { + updateGridAlpha() + + mapGridData?.gridUpdateDebouncer.closure = { _ in self.makeGrid() } + mapGridData?.gridUpdateDebouncer.execute(()) + } + + func makeGrid() { + + let sw = CLLocationCoordinate2D(latitude: region.center.latitude - region.span.latitudeDelta * 3.0, longitude: region.center.longitude - region.span.longitudeDelta * 3.0) + let ne = CLLocationCoordinate2D(latitude: region.center.latitude + region.span.latitudeDelta * 3.0, longitude: region.center.longitude + region.span.longitudeDelta * 3.0) + + // call w3w api for lines, if the area is not too great + if let distance = mapGridData?.w3w?.distance(from: sw, to: ne) { + if distance < W3WSettings.maxMetersDiagonalForGrid { + + mapGridData?.w3w?.gridSection(southWest:sw, northEast:ne) { lines, error in + self.makeNewGrid(lines: lines) + } + } + } + } + + func makeNewGrid(lines: [W3WLine]?) { + DispatchQueue.main.async { + self.makePolygons(lines: lines) + // replace the overlay with a new one with the new lines + if let overlay = mapGridData?.gridLines { + self.removeGrid() + addOverlay(overlay) + } + } + } + + func makePolygons(lines: [W3WLine]?) { + + var multiLine = [MKPolyline]() + + for line in lines ?? [] { + multiLine.append(MKPolyline(coordinates: [line.start, line.end], count: 2)) + } + mapGridData?.gridLines = W3WMapGridLines(multiLine) + } + + public func mapRenderer(overlay: MKOverlay) -> MKOverlayRenderer? { + + if let o = overlay as? W3WMapGridLines { + return getMapGridRenderer(overlay: o) + } + + if let o = overlay as? W3WMapSquareLines { + return getMapSquaresRenderer(overlay: o) + } + + return MKOverlayRenderer() + } + + func getMapGridRenderer(overlay: MKOverlay) -> MKOverlayRenderer? { + + if let gridLines = overlay as? W3WMapGridLines { + mapGridData?.gridRenderer = W3WMapGridRenderer(multiPolyline: gridLines) + mapGridData?.gridRenderer?.strokeColor = mapGridData?.mapGridColor.value.uiColor + mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.5) //mapGridData?.scheme?.styles?.lineThickness?.value ?? CGFloat(0.5) + updateGridAlpha() + return mapGridData?.gridRenderer + } + + return nil + } + + func getMapSquaresRenderer(overlay: MKOverlay) -> MKOverlayRenderer? { + + guard let mapGridData = self.mapGridData else { return nil } + + if let square = overlay as? W3WMapSquareLines { + let squareRenderer = W3WMapSquaresRenderer(overlay: square) + + let boxId = square.box.id //current square renderer + let isSelectedSquare = mapGridData.selectedSquare?.bounds?.id == boxId + + let isMarker = mapGridData.markers.contains(where: { $0.bounds?.id == square.box.id }) + + let isSquare = mapGridData.squares.contains(where: { $0.bounds?.id == square.box.id }) + + var bgSquareColor: W3WColor? = .w3wBrandBase + + if let coloredSquare = mapGridData.coloredPolylines.first(where: { $0.polyline === square }) { + bgSquareColor = coloredSquare.color + } + else { + if let color = mapGridData.overlayColors[boxId] { + bgSquareColor = color + + let coloredPolyline = ColoredPolyline(polyline: square, color: color) + mapGridData.coloredPolylines.append(coloredPolyline) + } + } + + let w3wImage: UIImage? + w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 25, height: 25)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 25, height: 25)) + + if (isSelectedSquare) { + if (isMarker == true) { + squareRenderer.lineWidth = 1.0 + squareRenderer.strokeColor = .black + // squareRenderer.setSquareImage(w3wImage1) + + if (isSquare == true) { // in list + squareRenderer.setSquareImage(w3wImage) + } + } + else { + squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor + squareRenderer.lineWidth = 0.1 + squareRenderer.setSquareImage(w3wImage) + } + + } else { + squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor + squareRenderer.lineWidth = 0.1 + squareRenderer.setSquareImage(w3wImage) + } + + mapGridData.squareRenderer = squareRenderer + return squareRenderer + + } + return nil + } + + /// remove the grid overlay + func removeGrid() { + for overlay in overlays { + if let gridOverlay = overlay as? W3WMapGridLines { + self.removeOverlay(gridOverlay) + } + } + + } + + func getPointsPerSquare() -> CGFloat { + let threeMeterMapSquare = MKCoordinateRegion(center: mapView!.centerCoordinate, latitudinalMeters: 3, longitudinalMeters: 3); + let threeMeterViewSquare = mapView!.convert(threeMeterMapSquare, toRectTo: nil) + + return threeMeterViewSquare.size.height + } + + func redrawAll() { + redrawPins() + redrawGrid() + redrawSquares() + } + + /// force a redrawing of all grid lines + func redrawGrid() { + makeGrid() + } + + /// force a redrawing of all annotations + func redrawPins() { + + // print("redrawPins: \(annotations.count)") + for annotation in annotations { + removeAnnotation(annotation) + addAnnotation(annotation) + } + } + + func redrawSquares() { + self.updateSquares() + } + + func updateGridAlpha() { + + var alpha = CGFloat(0.0) + + let pointsPerSquare = self.getPointsPerSquare() + if pointsPerSquare > CGFloat(11.0) { + alpha = (pointsPerSquare - CGFloat(11.0)) / CGFloat(11.001) + } + + if alpha > 1.0 { + alpha = 1.0 + + } else if alpha < 0.0 { + alpha = 0.0 + } + mapGridData?.gridRenderer?.alpha = alpha + + } + +} + +extension W3WAppleMapGridDrawingProtocol { + + // MARK: Pins / Annotations + + func getMapAnnotationView(annotation: MKAnnotation, transitionScale: CGFloat) -> MKAnnotationView? { + + if let a = annotation as? W3WAppleMapAnnotation { + let squareSize = getPointsPerSquare() + if squareSize > CGFloat(12.0) { + if let square = a.square { + showOutline(square, a) + } + //return an empty box + let box = MKAnnotationView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) + return box + } else { + if let square = a.square { + hideOutline(square) + } + return getMapPinView(annotation: a) + } + } + return nil + } + + + func showOutline(_ square: W3WSquare, _ annotation: W3WAppleMapAnnotation? = nil) { + W3WThread.runInBackground { + if let s = self.ensureSquareHasCoordinates(square: square), + let bounds = s.bounds, + let gridData = self.mapGridData { + W3WThread.runOnMain { + + if (annotation?.isMarker == true ) { + if let m = annotation?.square { + gridData.markers.removeAll() + gridData.markers.append(m) + } + if (annotation?.isSaved == true) { + self.addUniqueSquare(s) + } + } + else{ + self.addUniqueSquare(s) + } + + if let squareIsMarker = gridData.squareIsMarker { + self.addUniqueSquare(squareIsMarker) + } + + let boundsId = bounds.id + gridData.overlayColors[boundsId] = annotation?.color + + } + DispatchQueue.main.sync(flags: .barrier) { } + + self.updateSquares() + self.updateSelectedSquare() + } + } + } + + /// makes overlays from the squares + + func updateSquares() { + guard let gridData = self.mapGridData else { return } + + // Create a dispatch group to coordinate synchronization + let group = DispatchGroup() + + var colorsCopy = [Int64: W3WColor]() + + // Enter the group before starting work + group.enter() + + // Get colors from main thread + W3WThread.runOnMain { + colorsCopy = gridData.overlayColors + group.leave() // Signal that colors are ready + } + + // Wait for colors to be copied + group.wait() + + // Initialize the hash tracking variable if needed + if gridData.previousStateHash == nil { + gridData.previousStateHash = 0 + } + + // Create a comprehensive hash of the entire rendering state + var stateHasher = Hasher() + + // Hash the square IDs + for square in gridData.squares ?? [] { + if let id = square.bounds?.id { + stateHasher.combine(id) + } + } + + // Hash the colors + for (id, color) in colorsCopy { + stateHasher.combine(id) + stateHasher.combine(color.description) + } + + // Hash the selected square + if let selectedId = gridData.selectedSquare?.bounds?.id { + stateHasher.combine(selectedId) + } + + // Hash the markers commented out in your implementation + // for marker in gridData.markers { + // if let id = marker.bounds?.id { + // stateHasher.combine(id) + // } + // } + + let currentStateHash = stateHasher.finalize() + + // If nothing has changed, skip the update + if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 { + return + } + + // Update hash for next comparison + gridData.previousStateHash = currentStateHash + + var boxes = [(polyline: W3WMapSquareLines, color: W3WColor?)]() + + for square in gridData.squares ?? [] { + if let ne = square.bounds?.northEast, + let sw = square.bounds?.southWest { + + let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) + let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) + let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) + + polyline.associatedSquare = square + + let boxId = polyline.box.id ?? 0 + let color = colorsCopy[boxId] + + boxes.append((polyline: polyline, color: color)) + } + } + + if !gridData.coloredPolylines.isEmpty { + gridData.coloredPolylines.removeAll() + } + + // Create a local copy of boxes to ensure thread safety + let boxesCopy = boxes + + W3WThread.runOnMain { + self.removeSquareOverlays() + // Use boxesCopy instead of the original boxes array + for box in boxesCopy { + self.addOverlay(box.polyline, box.color) + } + } + } + + func updateSelectedSquare() { + guard let gridData = self.mapGridData else { return } + + let markers = gridData.markers + self.makeMarkers(markers) + } + + func makeMarkers(_ markers: [W3WSquare]?) { + + var boxes1 = [MKPolyline]() + + for (index, marker) in (markers ?? []).enumerated() { + + if let ne1 = marker.bounds?.northEast, + let sw1 = marker.bounds?.southWest { + + let nw1 = CLLocationCoordinate2D(latitude: ne1.latitude, longitude: sw1.longitude) + let se1 = CLLocationCoordinate2D(latitude: sw1.latitude, longitude: ne1.longitude) + boxes1.append(W3WMapSquareLines(coordinates: [nw1, ne1, se1, sw1, nw1], count: 5)) + } + } + + W3WThread.runOnMain { + + for bx in boxes1 { + addOverlay(bx) + } + } + } + + func findSquare(_ square: W3WSquare) -> W3WSquare? { + for s in self.mapGridData?.squares ?? [] { + if s.bounds?.id == square.bounds?.id { + return s + } + } + return nil + } + + func findMarker(_ square: W3WSquare) -> W3WSquare? { + for m in self.mapGridData?.markers ?? [] { + if m.bounds?.id == square.bounds?.id { + return m + } + } + return nil + } + + /// remove the grid overlay + func removeSquareOverlays() { + for overlay in overlays { + if let squareOverlay = overlay as? W3WMapSquareLines { + removeOverlay(squareOverlay) + } + } + } + + func hideOutline(_ words: String) { + self.mapGridData?.squares.removeAll(where: { s in + return s.words == words + }) + self.updateSquares() + } + + func getMapPinView(annotation: W3WAppleMapAnnotation) -> MKAnnotationView? { + return pinView(annotation: annotation) + } + + /// make a custom annotation view + func pinView(annotation: W3WAppleMapAnnotation) -> MKAnnotationView? { + + let identifier = "w3wPin" + let color : W3WColor? = annotation.color + var pinImage: UIImage? + let pinSize = CGFloat(40.0) + var centerOffset: CGPoint = .zero + let s = annotation.square + let w = s?.words + + if case .circle = annotation.type { + pinImage = W3WImage(drawing: .mapCircle, colors: .standardMaps.with(background: color)).get(size: W3WIconSize(value: CGSize(width: mapGridData?.pinWidth ?? CGFloat(30.0) , height: mapGridData?.pinHeight ?? CGFloat(30.0)))) + centerOffset = CGPoint(x: 0.0, y: 0.0) + } + + if case .square = annotation.type { + pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color)) + .get(size: W3WIconSize(value: CGSize(width: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0 , height: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0))) + centerOffset = CGPoint(x: 0.0, y: (-10)) + } + + + let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier) + annotationView.image = pinImage + annotationView.frame.size = CGSize(width:mapGridData?.pinWidth ?? CGFloat(35), height: mapGridData?.pinHeight ?? CGFloat(35)) + annotationView.centerOffset = centerOffset + + return annotationView + } +} + +extension W3WAppleMapGridDrawingProtocol { + + // MARK: SQUARES CHECK + + func ensureSquareHasCoordinates(square: W3WSquare) -> W3WSquare? { + let s = ensureSquaresHaveCoordinates(squares: [square]) + return s.first + } + + func ensureSquaresHaveCoordinates(squares: [W3WSquare]) -> [W3WSquare] { + //checkConfiguration() + if W3WThread.isMain() { + print(#function, " must NOT be called on main thread") + abort() + } + + var goodSquares = [W3WSquare]() + + let tasks = DispatchGroup() + + // for each square, make sure it is complete with coordinates and words + for square in squares { + tasks.enter() + complete(square: square) { completeSquare in + if let s = completeSquare { + goodSquares.append(s) + } + tasks.leave() + } + } + + // wait for all the squares to be completed + tasks.wait() + + return goodSquares + } + + func completeSquareWithCoordinates(square: W3WSquare) -> W3WSquare? { + let completedSquares = completeSquaresWithCoordinates(squares: [square]) + return completedSquares.first + } + + func convertToSquaresWithCoordinates(words: [String]) -> [W3WSquare] { + var squares = [W3WSquare]() + + for word in words { + squares.append(W3WBaseSquare(words: word)) + } + + return ensureSquaresHaveCoordinates(squares: squares) + } + + func convertToSquaresWithCoordinates(suggestions: [W3WSuggestion]) -> [W3WSquare] { + var squares = [W3WSquare]() + + for suggestion in suggestions { + squares.append(W3WBaseSquare(words: suggestion.words)) + } + + return ensureSquaresHaveCoordinates(squares: squares) + } + + func convertToSquares(coordinates: [CLLocationCoordinate2D]) -> [W3WSquare] { + var squares = [W3WSquare]() + + for coordinate in coordinates { + squares.append(W3WBaseSquare(coordinates: coordinate)) + } + + return ensureSquaresHaveCoordinates(squares: squares) + } + + func completeSquaresWithCoordinates(squares: [W3WSquare]) -> [W3WSquare] { + + if W3WThread.isMain() { + + let error = W3WError.message("must NOT be called on main thread") + // self.errorHandler(error: error) + print(#function, " must NOT be called on main thread") + abort() + } + + var completedSquares = [W3WSquare]() + let tasks = DispatchGroup() + + // for each square, make sure it is complete with coordinates and words + for square in squares { + tasks.enter() + complete(square: square) { completeSquare in + if let s = completeSquare { + completedSquares.append(s) + } + tasks.leave() + } + } + + // wait for all the squares to be completed + tasks.wait() + + return completedSquares + } + + /// check a square and fill out it's words or coordinates as needed, then return a completed square via completion block + func complete(square: W3WSquare, completion: @escaping (W3WSquare?) -> ()) { + + // if the square has words but no coordinates + if square.coordinates == nil { + if let words = square.words { + self.mapGridData?.w3w?.convertToCoordinates(words: words) { result, error in + // self.errorHandler(error: error) + completion(result) + } + + // else if the square has no words and no coordinates then it is useless and we omit it + } else { + completion(nil) + } + + // else if the square has coordinates but no words + } else if square.words == nil { + if let coordinates = square.coordinates { + self.mapGridData?.w3w?.convertTo3wa(coordinates: coordinates, language: self.mapGridData?.language ?? W3WSettings.defaultLanguage ) { result, error in + // self.errorHandler(error: error) + completion(result) + } + + // else if the square has no words and no coordinates then it is useless and we omit it + } else { + completion(nil) + } + + // else the square already has coordinates and words + } else { + completion(square) + } + } + + /// put a what3words annotation on the map showing the address + func addMarker(at words: String?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let w = words { + self.mapGridData?.w3w?.convertToCoordinates(words: w) { square, error in + // self.errorHandler(error: error) + if let s = square { + self.addMarker(at: s, color: color, type: type) + } + } + } + } + } + + /// put a what3words annotation on the map showing the address + public func addMarker(at words: [String]?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let w = words { + W3WThread.runInBackground { + let squaresWithCoords = self.convertToSquaresWithCoordinates(words: w) + + // if there's a bad square then error out + if squaresWithCoords.count != words?.count { + completion(nil, W3WError.message("Invalid three word address, or coordinates passed into map function")) + + // otherwise go for it + } else { + self.addMarker(at: squaresWithCoords, color: color, type: type) + } + } + } + } + } + + + /// put a what3words annotation on the map showing the address, and optionally center the map around it + func addMarker(at square: W3WSquare?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let sq = square { + W3WThread.runInBackground { + if let s = self.completeSquareWithCoordinates(square: sq) { + self.addAnnotation(square: s, color: color, type: type) + completion(s , nil) + } + } + } + } + } + + ///////////// + /// + func addSelectedMarker(at square: W3WSquare?, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let sq = square { + W3WThread.runInBackground { + if let s = self.completeSquareWithCoordinates(square: sq) { + self.addAnnotation(square: s, color: color, type: type, isMarker: isMarker, isMark: isMark, isSaved: isSaved) + completion(s , nil) + } + } + } + } + } + + + + /// add an annotation to the map given a square this compensates for missing words or missing + /// coordiantes, and does nothing if neither is present + /// this is the one that actually does the work. The other addAnnotations calls end up calling this one. + func addAnnotation(square: W3WSquare, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false) { + W3WThread.runOnMain { + W3WThread.runInBackground { + if let s = self.completeSquareWithCoordinates(square: square) { + W3WThread.runOnMain { + self.removeMarker(at: square) + addAnnotation(W3WAppleMapAnnotation(square: s, color: color, type: type, isMarker: isMarker, isMark: isMark, isSaved: isSaved)) + } + } + } + } + } + + func addCirclePin(square: W3WSquare, color: W3WColor? = nil) { + W3WThread.runOnMain { + // W3WThread.runInBackground { + addAnnotation(W3WAppleMapAnnotation(square: square, color: color, type: .circle, isMarker: false, isMark: false )) + // } + } + } + + func addMarkerAsCircle(at square: W3WSquare?, color: W3WColor? = nil, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + // W3WThread.runInBackground { + if let s = square { + self.addCirclePin(square: s, color: color) + completion(s , nil) + } + // } + } + } + + /// put a what3words annotation on the map showing the address + func addMarker(at suggestion: W3WSuggestion?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + if let words = suggestion?.words { + addMarker(at: words, color: color, type: type, completion: completion) + } + } + + /// put a what3words annotation on the map showing the address + public func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let s = suggestions { + W3WThread.runInBackground { + let squaresWithCoords = self.convertToSquaresWithCoordinates(suggestions: s) + + // if there's a bad square then error out + if squaresWithCoords.count != suggestions?.count { + completion(nil, W3WError.message("Invalid three word address, or coordinates passed into map function")) + + // otherwise go for it + } else { + self.addMarker(at: squaresWithCoords, color: color, type: type, completion: completion) + } + } + } + } + } + + + /// put a what3words annotation on the map showing the address + func addMarker(at coordinates: CLLocationCoordinate2D?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let c = coordinates { + // self.checkConfiguration() + self.mapGridData?.w3w?.convertTo3wa(coordinates: c, language: self.mapGridData?.language ?? W3WSettings.defaultLanguage) { square, error in + // self.dealWithAnyApiError(error: error) + if let s = square { + self.addMarker(at: s, color: color, type: type, completion: completion) + } + } + } + } + } + + /// put a what3words annotation on the map showing the address + public func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + W3WThread.runOnMain { + if let c = coordinates { + W3WThread.runInBackground { + let squaresWithCoords = self.convertToSquares(coordinates: c) + + // if there's a bad square then error out + if squaresWithCoords.count != coordinates?.count { + completion(nil, W3WError.message("Invalid three word address, or coordinates passed into map function")) + + // otherwise go for it + } else { + self.addMarker(at: squaresWithCoords, color: color, type: type, completion: completion) + } + } + } + } + } + + + + /// put a what3words annotation on the map showing the address + func addMarker(at squares: [W3WSquare]?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { + + W3WThread.runOnMain { + if let s = squares { + W3WThread.runInBackground { + let goodSquares = self.ensureSquaresHaveCoordinates(squares: s) + + // error out if not all squares are valid + if goodSquares.count != s.count { + completion(nil, W3WError.message("Invalid three word address, or coordinates passed into map function")) + + // good squares, proceed to place on map + } else { + completion(goodSquares as! W3WSquare, nil) + } + } + } + } + } +} + + +extension W3WAppleMapGridDrawingProtocol { + + /// remove a what3words annotation from the map if it is present + public func removeMarker(at suggestion: W3WSuggestion?) { + if let words = suggestion?.words { + removeMarker(at: words) + } + } + + public func removeMarker(at words: String?) { + if let w = words { + for annotation in annotations { + if let a = annotation as? W3WAppleMapAnnotation { + if let square = a.square { + if square.words == w { + self.removeAnnotation(a) + self.hideOutline(w) + } + } + } + } + } + } + + /// remove what3words annotations from the map if they are present + public func removeMarker(at suggestions: [W3WSuggestion]?) { + for suggestion in suggestions ?? [] { + removeMarker(at: suggestion) + } + } + + /// remove what3words annotations from the map if they are present + public func removeMarker(at squares: [W3WSquare]?) { + for square in squares ?? [] { + removeMarker(at: square) + } + } + + /// remove what3words annotations from the map if they are present + public func removeMarker(at words: [String]?) { + for word in words ?? [] { + removeMarker(at: word) + } + } + + /// remove a what3words annotation from the map if it is present + /// this is the one that actually does the work. The other remove calls + /// end up calling this one. + public func removeMarker(at square: W3WSquare?) { + if let s = square { + if let annotation = findAnnotation(s) { + removeAnnotation(annotation) + hideOutline(s) + } + } + } + + func hideOutline(_ square: W3WSquare) { + W3WThread.runInBackground { + if var squares = self.mapGridData?.squares { + squares.removeAll(where: { s in + return s.bounds?.id == square.bounds?.id + }) + } + +// for anno in annotations { +// if let a = anno as? W3WAppleMapAnnotation { +// print(a.square?.words) +// print(annotations.count) +// } +// } + + self.updateSquares() + } + + } + + func hideOutlineMarker(_ square: W3WSquare) { + + W3WThread.runInBackground { + if let s = self.mapGridData?.markers { + if s.count != 0 { + self.mapGridData?.markers.removeAll(where: { m in + return m.bounds?.id == square.bounds?.id + }) + }// self.mapGridData?.markers + + } + self.updateSelectedSquare() + // self.updateSquares() + } + + } + + public func removeSelectedSquare(at square: W3WSquare?) { + if let s = square { + if let annotation = findAnnotation(s) { + removeAnnotation(annotation) + hideOutlineMarker(s) + } + } + } + + + func findAnnotation(_ square: W3WSquare?) -> W3WAppleMapAnnotation? { + for annotation in annotations { + if let a = annotation as? W3WAppleMapAnnotation { + if (a.square?.bounds?.id == square?.bounds?.id) { + return a + } + } + } + + return nil + } + + /// remove what3words annotations from the map if they are present + public func removeAllMarkers() { + for annotation in annotations { + if let w3wAnnotation = annotation as? W3WAppleMapAnnotation { + removeAnnotation(w3wAnnotation) + if let square = w3wAnnotation.square { + hideOutline(square) + } + } + } + } + + public func getAllMarkers() -> [W3WSquare] { + var squares = [W3WSquare]() + + for annotation in annotations { + if let a = annotation as? W3WAppleMapAnnotation { + if let square = a.square { + squares.append(square) + } + } + } + + return squares + } + +} + +extension W3WAppleMapGridDrawingProtocol { + + /// set the map center to a coordinate, and set the minimum visible area + func set(center: CLLocationCoordinate2D) { + W3WThread.runOnMain { + self.setCenter(center, animated: true) + } + } + + + /// set the map center to a coordinate, and set the minimum visible area + func set(center: CLLocationCoordinate2D, latitudeSpanDegrees: Double, longitudeSpanDegrees: Double) { + W3WThread.runOnMain { + let coordinateRegion = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: latitudeSpanDegrees, longitudeDelta: longitudeSpanDegrees)) + self.setRegion(coordinateRegion, animated: true) + } + } + + + /// set the map center to a coordinate, and set the minimum visible area + func set(center: CLLocationCoordinate2D, latitudeSpanMeters: Double, longitudeSpanMeters: Double) { + W3WThread.runOnMain { + let coordinateRegion = MKCoordinateRegion(center: center, latitudinalMeters: latitudeSpanMeters, longitudinalMeters: longitudeSpanMeters) + self.setRegion(coordinateRegion, animated: true) + } + } + + func addUniqueSquare(_ square: any W3WSquare) { + + if let gridData = self.mapGridData { + + let exists = gridData.squares.contains { existingSquare in + return existingSquare.bounds?.id == square.bounds?.id + } + + // Only add if it doesn't exist + if !exists { + gridData.squares.append(square) + } + } + } + + func removeAllSquares() { + if var gridData = self.mapGridData { + gridData.squares.removeAll() + gridData.markers.removeAll() + gridData.selectedSquare = nil + gridData.squareIsMarker = nil + gridData.currentSquare = nil + } + } +} + + +#endif diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift new file mode 100644 index 0000000..5600663 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -0,0 +1,530 @@ +// +// W3WAppleMapHelper.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 16/12/24. +// + +import MapKit +import Foundation +import W3WSwiftThemes +import W3WSwiftCore +import W3WSwiftComponentsMap +import W3WSwiftDesign + +public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WAppleMapHelperProtocol { + + public weak var mapView: MKMapView? + + public var mapGridData: W3WAppleMapGridData? + + public var scheme: W3WScheme? = .w3w + + public var region: MKCoordinateRegion { + return mapView?.region ?? MKCoordinateRegion() + } + + public var annotations: [MKAnnotation] { + return mapView?.annotations ?? [MKAnnotation]() + } + + public var overlays: [MKOverlay] { + get { + return mapView?.overlays ?? [MKOverlay]() + } + } + + public var mapType: MKMapType { + get { + return mapView?.mapType as! MKMapType + } + set { + + mapView?.mapType = newValue + + self.redrawAll() + setGridColor() + } + } + + public var language: W3WLanguage = W3WSettings.defaultLanguage + + + /// called when the user taps a square in the map + public var onSquareSelected: (W3WSquare) -> () = { _ in } + + /// called when the user taps a square that has a marker added to it + public var onMarkerSelected: (W3WSquare) -> () = { _ in } + + private var w3w: W3WProtocolV4 + + public private(set) var markers: [W3WSquare] = [] + + // typealias MarkerCompletion = (W3WSquare?, W3WError?) -> () + + + public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { + self.mapView = mapView + self.w3w = w3w + super.init() + + self.mapGridData = W3WAppleMapGridData(w3w: w3w, scheme: scheme, language: language) + self.language = language + + } + + func setGridColor() { + if let gridData = mapGridData { + gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) + } + } + + func setGridLine() { + if let gridData = mapGridData { + gridData.mapGridLineThickness.send(2.0) + } + } + + func configure () { + self.mapView?.showsUserLocation = true + } + + public func set(language: W3WLanguage) { + self.language = language + } + + public func set(type: String) { + switch type { + case "standard": self.mapType = .standard + case "hybrid": self.mapType = .hybrid + case "satellite": self.mapType = .satellite + + default: self.mapType = .standard + } + } + + public func set(scheme: W3WScheme?) { + self.mapGridData?.set(scheme: scheme) + } + + public func getType() -> W3WMapType { + switch self.mapType { + case .standard: return "standard" + case .satellite: return "hybridFlyover" + case .hybrid: return "hybrid" + + default: return "standard" + } + } + + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + updateMap() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + updateMap() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { + + if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { + return a + } + + return nil + } + + + public func addAnnotation(_ annotation: MKAnnotation) { + mapView?.addAnnotation(annotation) + } + + public func removeAnnotation(_ annotation: MKAnnotation) { + mapView?.removeAnnotation(annotation) + } + + public func removeOverlay(_ overlay: MKOverlay) { + mapView?.removeOverlay(overlay) + } + + public func removeOverlays(_ overlay: [MKOverlay]) { + mapView?.removeOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay) { + mapView?.addOverlay(overlay) + } + + public func addOverlays(_ overlays: MKOverlay) { + mapView?.addOverlay(overlays) + } + + public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { + mapView?.addOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { + + if let color = color, let square = overlay as? W3WMapSquareLines { + + let coloredPolyline = ColoredPolyline(polyline: square, color: color) + mapGridData?.coloredPolylines.append(coloredPolyline) + } + + mapView?.addOverlay(overlay) + } + + + public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + if let markerView = view.annotation as? W3WAppleMapAnnotation { + if let square = markerView.square { + } + } + } + + public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { + + } + + public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { + + } + +} + + +public extension W3WAppleMapHelper { + + func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { + + self.convertTo3wa(coordinates: coordinates ?? CLLocationCoordinate2D(), language: self.language) { [weak self] square, error in + + guard let self = self else { return } + + if let e = error { + W3WThread.runOnMain { + self.mapGridData?.onError(e) + completion(.failure(e)) + } + } + if let s = square { + W3WThread.runOnMain { + self.select(at: s) + completion(.success(s)) + } + } else { + W3WThread.runOnMain { + let e = W3WError.message("No Square Found") + completion(.failure(e)) + } + } + } + } + + func select(at: W3WSquare) { + createMarkerForCondition(at: at) + + //sample code to update markers + // let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) + // markersList.add(listName: "favorites", color: .w3wBrandBase) + // markersList.add(square: at, listName: "favorites") + + // self.mapGridData?.savedList = markersList + + // completion(markersList) + ///Sample code to add + +// var randomColors: [W3WColor] = [] +// +// for _ in 0..<10 { +// let red = CGFloat.random(in: 0...1) +// let green = CGFloat.random(in: 0...1) +// let blue = CGFloat.random(in: 0...1) +// let uiColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0) +// let randomColor = W3WColor(uiColor: uiColor) +// +// randomColors.append(randomColor) +// } +// +// let randomIndex = Int.random(in: 0.. self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { + + if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square + let previousBoxId = selectedSquare.bounds?.id + + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: previousColor) + } + } + else{ + removeSelectedSquare(at: selectedSquare) + } + let previousBoxId = selectedSquare.bounds?.id + addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) + self.mapGridData?.selectedSquare = at + + return + } + removeSelectedSquare(at: selectedSquare) + } + + //squares + if isMarkerinList == true { + removeSelectedSquare(at: selectedSquare) + let currentBoxId = at.bounds?.id + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarker(at: selectedSquare, color: previousColor, type: .circle) + } + } + if let color = self.mapGridData?.overlayColors[currentBoxId ?? 0] { + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: false) + } + self.mapGridData?.squareIsMarker = at + + } else { + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarkerAsCircle(at: selectedSquare, color: previousColor) + // addSelectedMarker(at: selectedSquare, color: previousColor, type: .circle, isMarker: true, isMark: true) + } + } + addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) + } + + self.mapGridData?.selectedSquare = at + } + + /// put a what3words annotation on the map showing the address + func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: square, color: color, type: type, completion: completion) + } + + func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestion, color: color, type: type, completion: completion) + } + + func addMarker(at word: String?, color: W3WSwiftThemes.W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + + addMarker(at: word, color: color, type: type, completion: completion) + } + + func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: words, color: color, type: type, completion: completion) + } + + func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinate, color: color, type: type, completion: completion) + } + + func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: squares, color: color, type: type, completion: completion) + } + + func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestions, color: color, type: type, completion: completion) + } + + func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinates, color: color, type: type, completion: completion) + } + + func removeMarker(at suggestion: W3WSuggestion?) { + + } + + func removeMarker(at words: String?) { + + } + + func removeMarker(at squares: [W3WSquare]?) { + + } + + func removeMarker(at suggestions: [W3WSuggestion]?) { + + } + + func removeMarker(at words: [String]?) { + + } + + func removeMarker(at square: W3WSquare?) { + + } + + func removeMarker(group: String) { + + } + + func unselect() { + + } + + func hover(at: CLLocationCoordinate2D) { + + } + + func unhover() { + + } + + func set(zoomInPointsPerSquare: CGFloat) { + + } + + public func getAllMarkers() -> [W3WSquare] { + return [W3WSquare]() + } + + public func removeAllMarkers() { + self.markers.removeAll() + + if var gridData = self.mapGridData { + gridData.squares.removeAll() + gridData.markers.removeAll() + gridData.selectedSquare = nil + gridData.squareIsMarker = nil + gridData.currentSquare = nil + } + } + public func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { + return nil + } + + +} + +extension W3WAppleMapHelper { + + public func updateCamera(camera: W3WMapCamera?) { + +// W3WThread.runOnMain { [weak self] in +// if let self = self { +// if let center = camera?.center, let scale = camera?.scale { +// let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) +// mapView?.setRegion(region, animated: true) +// +// } else if let center = camera?.center { +// mapView?.setCenter(center, animated: true) +// +// } else if let scale = camera?.scale { +// let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) +// mapView?.setRegion(region, animated: true) +// } +// } +// } + } + + public func updateSquare(square: W3WSquare?) { + + } + + public func updateMarkers(markers: W3WMarkersLists) { + removeAllMarkers() + for (_, list) in markers.getLists() { + for marker in list.markers { + // addMarker(at: marker, color: list.color, type: .circle) + print(marker.words, list.color) + } + } + } + + public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { + + self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in + guard let self = self else { return } + + if let error = error { + W3WThread.runOnMain { + completion(nil, error) + } + } else if let s = square { + W3WThread.runOnMain { + completion(s, nil) + } + } + } + } + + func findSquare(_ square: W3WSquare) -> W3WSquare? { + return (self.mapGridData?.squares ?? []).first { s in + let idMatch = s.bounds?.id == square.bounds?.id + return idMatch + } + return nil + } +} + +extension W3WAppleMapHelper { + + public func setRegion(_ region: MKCoordinateRegion, animated: Bool) { + mapView?.setRegion(region, animated: false) + } + + public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { + mapView?.setCenter(coordinate, animated: animated) + } + + public func updateZoomLevel() { + + } + +} + + +extension W3WAppleMapHelper { + + func findAnnotation(_ square: W3WSquare?) -> W3WAppleMapAnnotation? { + for annotation in annotations { + if let a = annotation as? W3WAppleMapAnnotation { + if (a.square?.bounds?.id == square?.bounds?.id) { + return a + } + } + } + + return nil + } +} diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelperProtocol.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelperProtocol.swift new file mode 100644 index 0000000..5cca029 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelperProtocol.swift @@ -0,0 +1,59 @@ +// +// W3WAppleMapHelperProtocol.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 17/12/24. +// +import Foundation +import CoreLocation +import W3WSwiftCore +import W3WSwiftThemes +import W3WSwiftComponentsMap + +public protocol W3WAppleMapHelperProtocol { + + // put a what3words annotation on the map showing the address + func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + + func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at words: String?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) + + // remove what3words annotations from the map if they are present + func removeMarker(at suggestion: W3WSuggestion?) + func removeMarker(at words: String?) + func removeMarker(at squares: [W3WSquare]?) + func removeMarker(at suggestions: [W3WSuggestion]?) + func removeMarker(at words: [String]?) + func removeMarker(at square: W3WSquare?) + func removeMarker(group: String) + + // show the "selected" outline around a square + func select(at: W3WSquare) + + // remove the selection from the selected square + func unselect() + + // show the "hover" outline around a square + func hover(at: CLLocationCoordinate2D) + + // hide the "hover" outline around a square + func unhover() + + // get the list of added squares + func getAllMarkers() -> [W3WSquare] + + // remove what3words annotations from the map if they are present + func removeAllMarkers() + + // find a marker by it's coordinates and return it if it exists in the map + func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? + + // sets the size of a square after .zoom is used in a show() call + func set(zoomInPointsPerSquare: CGFloat) + +} diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapAnnotation.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapAnnotation.swift new file mode 100644 index 0000000..9a3aeb7 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapAnnotation.swift @@ -0,0 +1,58 @@ +// +// W3WAnnotation.swift +// w3w-swift-components-map +// +// Created by Henry Ng on 13/1/25. +// + +#if !os(macOS) && !os(watchOS) + +import Foundation +import MapKit +import W3WSwiftCore +import W3WSwiftThemes +import W3WSwiftComponentsMap + +public class W3WAppleMapAnnotation: MKPointAnnotation { + + var square: W3WSquare? + + var type: W3WMarkerType? = .circle + + var color: W3WColor? + + var isMarker: Bool? + + var isMark: Bool? + + var isSaved: Bool? + + + public init(square: W3WSquare, color: W3WColor? = nil, type: W3WMarkerType? = .circle, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false) { + + super.init() + + self.color = color + self.type = type + self.isMarker = isMarker + self.isMark = isMark + self.square = square + self.isSaved = isSaved + + if let words = square.words { + if W3WSettings.leftToRight { + title = "///" + words + } else { + title = words + "///" + } + } + + if let coordinates = square.coordinates { + self.coordinate = coordinates + } + + } +} + + +#endif diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift new file mode 100644 index 0000000..8a15082 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -0,0 +1,234 @@ +// +// W3WAppleMapGridData.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 18/1/25. +// + +#if !os(macOS) && !os(watchOS) + +import Foundation +import MapKit +import W3WSwiftCore +import W3WSwiftThemes +import Combine +import W3WSwiftComponentsMap + + +public class W3WAppleMapGridData { + + private var cancellables = Set() + + public let squareColor = W3WLive(.w3wBrandBase) + + public let mapGridColor = W3WLive(.mediumGrey) + public let mapGridLineThickness = W3WLive(0.5) + + public let mapSquareColor = W3WLive(.black) + public let mapSquareLineThickness = W3WLive(0.1) + + public let selectedSquareBorderColor = W3WLive(.black) + public let selectedSquareThickness = W3WLive(0.5) + + public let pinWidth: CGFloat = CGFloat(35.0) + public let pinHeight: CGFloat = CGFloat(35.0) + public let pinSize = CGFloat(40.0) + public var onError: W3WMapErrorHandler = { _ in } + + var gridRendererPointer: W3WMapGridRenderer? = nil + var squareRendererPointer: W3WMapSquaresRenderer? = nil + + var gridLinePointer: W3WMapGridLines? = nil + + + + var w3w: W3WProtocolV4? + + /// language to use currently + var language: W3WLanguage = W3WSettings.defaultLanguage + + /// highighted individual squares on the map + var squares = [W3WSquare]() + + var markers = [W3WSquare]() + + var savedList = W3WMarkersLists() + + var selectedSquare: W3WSquare? = nil + + var squareIsMarker: W3WSquare? = nil + + var currentSquare: W3WSquare? = nil + + public var currentOverlays: [Int64: MKOverlay] = [:] + + public var overlayColors: [Int64: W3WColor] = [:] + + public var previousSquareIds = Set() + public var previousSquareIdsHash: Int? + public var previousStateHash: Int? + public var coloredPolylines = [ColoredPolyline]() + + public var scheme: W3WScheme? = .w3w + + var mapZoomLevel = CGFloat(0.0) + var pointsPerSquare = CGFloat(12.0) + + /// keep track of the zoom level so we can change pins to squares at a certain point + var lastZoomPointsPerSquare = CGFloat(0.0) + + var visibleZoomPointsPerSquare = CGFloat(32.0) + + var gridRenderer: W3WMapGridRenderer? { + get { return gridRendererPointer } + set { gridRendererPointer = newValue } + } + + var squareRenderer: W3WMapSquaresRenderer? { + get { return squareRendererPointer } + set { squareRendererPointer = newValue } + } + + + var gridLines: W3WMapGridLines? { + get { return gridLinePointer } + set { gridLinePointer = newValue } + } + + var gridUpdateDebouncer = W3WDebouncer(delay: 0.3, closure: { _ in }) + + public init(w3w: W3WProtocolV4, scheme: W3WScheme? = .w3w, language: W3WLanguage = W3WSettings.defaultLanguage) { + + self.w3w = w3w + self.scheme = scheme + self.language = language + + self.mapGridColorListener() + self.squareColorListener() + self.squareLineThicknessListener() + } + + private func mapGridColorListener() { + mapGridColor + .sink { [weak self] color in + self?.gridRenderer?.strokeColor = color.uiColor + } + .store(in: &cancellables) + } + + private func squareColorListener() { + squareColor + .sink { [weak self] color in + self?.squareRendererPointer?.strokeColor = color.uiColor + } + .store(in: &cancellables) + } + + private func squareLineThicknessListener() { + mapSquareLineThickness + .sink { [weak self] lineThickness in + self?.squareRendererPointer?.lineWidth = lineThickness.value + } + .store(in: &cancellables) + } + + public func set(scheme: W3WScheme?) { + self.scheme = scheme + } + + public func set(language: W3WLanguage) { + self.language = language + } + +} + +public class W3WMapGridLines: MKMultiPolyline { +} + +public class W3WMapGridRenderer: MKMultiPolylineRenderer { +} + +public class ColoredPolyline { + + let polyline: W3WMapSquareLines + let color: W3WColor? + + init(polyline: W3WMapSquareLines, color: W3WColor?) { + self.polyline = polyline + self.color = color + } + +} + + +public class W3WMapSquareLines: MKPolyline { + + var associatedSquare: W3WSquare? + + var box: W3WBaseBox { + let points = self.points() + let sw = points[3].coordinate // SW is at index 3 + let ne = points[1].coordinate // NE is at index 1 + return W3WBaseBox(southWest: sw, northEast: ne) + } + + convenience init? (bounds: W3WBaseBox?, square: W3WSquare? = nil) { + guard let ne = bounds?.northEast, + let sw = bounds?.southWest else { + return nil + } + + let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) + let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) + let coordinates = [nw, ne, se, sw, nw] + + self.init(coordinates: coordinates, count: 5) + self.associatedSquare = square + } +} + +public class W3WMapSquaresRenderer: MKPolylineRenderer { + + private var squareW3WImage: UIImage? + + // Cache the bounding rect to avoid recalculating it + private var cachedBoundingRect: CGRect? + + override public func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { + super.draw(mapRect, zoomScale: zoomScale, in: context) + + // Only proceed with drawing if we have an image + guard let w3wImage = squareW3WImage else { + return + + } + + // Calculate the display rect if needed + let displayRect = cachedBoundingRect ?? { + let rect = self.rect(for: self.polyline.boundingMapRect) + let imageRect = rect.insetBy(dx: self.lineWidth, dy: self.lineWidth) + cachedBoundingRect = imageRect + return imageRect + }() + + // Use more efficient drawing methods + UIGraphicsPushContext(context) + context.saveGState() + w3wImage.draw(in: displayRect, blendMode: .normal, alpha: 1.0) + context.restoreGState() + UIGraphicsPopContext() + } + + // Method to set the W3WImage + public func setSquareImage(_ w3wImage: UIImage?) { + + self.squareW3WImage = w3wImage + self.cachedBoundingRect = nil // Invalidate cached rect + self.setNeedsDisplay() + } + + + +} + +#endif diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapTypes.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapTypes.swift new file mode 100644 index 0000000..acfd695 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapTypes.swift @@ -0,0 +1,14 @@ +// +// W3WMapError.swift +// w3w-swift-components-map +// +// Created by Henry Ng on 2/1/25. +// + + +import W3WSwiftCore + +/// error response code block definition +public typealias W3WMapErrorHandler = (W3WError) -> () + +public typealias MarkerCompletion = (W3WSquare?, W3WError?) -> () diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAreaMath.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAreaMath.swift new file mode 100644 index 0000000..757d6f0 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAreaMath.swift @@ -0,0 +1,62 @@ +// +// W3WAearMath.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 7/2/25. +// + + +import Foundation +import CoreLocation + + +/// given coordinates, find the center and span containing all of them +class W3WAreaMath { + + var middle = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0) + var count = 0 + var minLat = Double.infinity + var minLng = Double.infinity + var maxLat = -Double.infinity + var maxLng = -Double.infinity + + + /// add a coordinate to the list + func add(coordinates: CLLocationCoordinate2D) { + if minLat > coordinates.latitude { + minLat = coordinates.latitude + } + if minLng > coordinates.longitude { + minLng = coordinates.longitude + } + if maxLat < coordinates.latitude { + maxLat = coordinates.latitude + } + if maxLng < coordinates.longitude { + maxLng = coordinates.longitude + } + + count += 1 + } + + + /// return the center of the group of all the coordinates + func getCenter() -> CLLocationCoordinate2D { + if count > 0 { + middle.latitude = (maxLat - minLat) / 2.0 + minLat + middle.longitude = (maxLng - minLng) / 2.0 + minLng + } + + return middle + } + + + /// return the span from the center of the group in lat,lng + func getSpan() -> (Double, Double) { + let latSpan = min(max(-90.0, (maxLat - minLat) * 1.5), 90.0) + let longSpan = min(max(-180.0, (maxLng - minLng) * 1.5), 180.0) + + return (latSpan, longSpan) + } + +} diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift new file mode 100644 index 0000000..f5df15e --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift @@ -0,0 +1,34 @@ +// +// W3WImageCache.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 3/3/25. +// + +import UIKit +import W3WSwiftThemes + + +class W3WImageCache { + static let shared = W3WImageCache() + private var cache = NSCache() + + func getImage(for color: W3WColor, size: CGSize) -> UIImage? { + let key = "\(color.description)_\(size.width)_\(size.height)" as NSString + + if let cachedImage = cache.object(forKey: key) { + return cachedImage + } + + // Get the image + let newImage = W3WImage(drawing: .mapSquare, colors: .standardMaps.with(background: color)) + .get(size: W3WIconSize(value: size)) + + // Cache it if it's not nil + cache.setObject(newImage, forKey: key) + + return newImage + } +} + + diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift new file mode 100644 index 0000000..e94c46e --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -0,0 +1,358 @@ +// +// W3WAppleMapView.swift +// +// +// Created on 03/12/2024. +// + +import MapKit +import W3WSwiftCore +import W3WSwiftComponentsMap +import W3WSwiftCore +import W3WSwiftApi +import W3WSwiftDesign + + +/// An Apple Map Kit Map +public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapViewProtocol, W3WEventSubscriberProtocol { + + + public var transitionScale = W3WMapScale(pointsPerMeter: CGFloat(4.0)) + + // public var transitionScaleConversion = transitionScale.value + + public var subscriptions = W3WEventsSubscriptions() + + /// The map view model to use + public var viewModel: W3WMapViewModelProtocol + + var helper: W3WAppleMapGridDrawingProtocol! + + typealias W3WHelper = W3WAppleMapHelper + + private var w3wHelper: W3WHelper { helper as! W3WHelper } + + private var onError: W3WMapErrorHandler = { _ in } + + var zoomLevel: Double { + let zoomScale = self.visibleMapRect.size.width / Double(self.frame.size.width) + let zoomExponent = log2(zoomScale) + return 20 - zoomExponent + } + + // var output: W3WEvent + + // var mapView: MKMapView? + + /// The available map types + public var types: [W3WMapType] { get { return [.standard, .satellite, .hybrid] } } + + /// Make an Apple Map Kit Map + /// - Parameters + /// - viewModel: The viewModel to use + public init(viewModel: W3WMapViewModelProtocol) { + + self.viewModel = viewModel + super.init(frame: .w3wWhatever) + self.helper = W3WAppleMapHelper(mapView: self, viewModel.w3w) as! any W3WAppleMapGridDrawingProtocol + + configure() + } + + func configure() { + + delegate = self + + set(viewModel: self.viewModel) + + set(type: .hybrid) + + bind() + + tesFuncs() + + } + + func tesFuncs() { + + addTestMarkers() + attachTapRecognizer() + } + + /// Make an Apple Map Kit Map + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Change the viewModel for this map. Typically used when + /// switching maps in the map view + /// - Parameters + /// - viewModel: The viewModel to use + public func set(viewModel: W3WMapViewModelProtocol) { + self.viewModel = viewModel + } + + public func set(type: String) { + w3wHelper.set(type: type) + } + + public func getType() -> W3WMapType { + + let type = w3wHelper.getType() + switch type { + case .standard: return "standard" + case .satellite: return "satellite" + case .hybrid: return "hybrid" + + default: return "hybridFlyover" + } + } + + public func getCameraState() -> W3WMapCamera { + let mapView = w3wHelper.mapView + + return W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size )) + } + + public func set(scheme: W3WScheme?) { + + w3wHelper.set(scheme: scheme) + + // w3wHelper.gridData?.gridRenderer?.lineWidth = scheme?.styles?.lineThickness?.value ?? CGFloat(0.5) + // w3wHelper.gridData!.gridRenderer?.strokeColor = scheme?.colors?.line?.current.uiColor + + } + + public func updateSavedLists(markers: W3WMarkersLists) { + viewModel.mapState.markers.send(markers) + } + + func bind() { + + subscribe(to: self.viewModel.mapState.markers) { [weak self] markers in + guard let self = self else { return } + + w3wHelper.updateMarkers(markers: markers) + } + + subscribe(to: self.viewModel.mapState.camera) { [weak self] camera in + guard let self = self else { return } + + w3wHelper.updateCamera(camera: camera) + } + + subscribe(to: self.viewModel.mapState.selected) { [weak self] square in + guard let self = self else { return } + + w3wHelper.updateSquare(square: square) + } + + subscribe(to: self.viewModel.gps) { [weak self] gps in + guard let self = self else { return } + } + + } + + func addTestMarkers(){ + + w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .w3wBrandBase, type: .circle)// + w3wHelper.addMarker(at: "outgrown.onions.marathon", color: .brown, type: .circle)// +// w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// +// w3wHelper.addMarker(at: "dugouts.flaking.pleasing", color: .brightGreen, type: .circle)// +// w3wHelper.addMarker(at: "humans.stags.insulated", color: .darkGreen, type: .circle) // +// w3wHelper.addMarker(at: "melts.luggage.cuter", color: .systemPurple, type: .circle) //// +// w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle) +// w3wHelper.addMarker(at: "bleaching.urgent.heartache", color: .aqua, type: .circle)// +// w3wHelper.addMarker(at: "allowable.realm.crafted", color: .lightCyan, type: .circle)// +// w3wHelper.addMarker(at: "admire.couch.celebrate", color: .aqua, type: .circle)// +// w3wHelper.addMarker(at: "listen.searching.reviews", color: .charcoal, type: .circle)// +// w3wHelper.addMarker(at: "vaulting.lasts.birthing", color: .dullRed, type: .circle)// +// w3wHelper.addMarker(at: "sleeping.beanbag.contour", color: .darkBlue , type: .circle)// +// w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle)// +// w3wHelper.addMarker(at: "often.opts.blushes", color: .darkBlueAlpha60, type: .circle)// +// w3wHelper.addMarker(at: "skinny.bound.reclusive", color: .w3wSuccessLabelDark, type: .circle)// +// w3wHelper.addMarker(at: "segregate.unwraps.majors", color: .mustard, type: .circle)// +// w3wHelper.addMarker(at: "losses.pheasants.eagle", color: .systemYellow, type: .circle) +// w3wHelper.addMarker(at: "protected.nylon.will", color: .w3wErrorBase, type: .circle)// +// w3wHelper.addMarker(at: "majors.supplier.playing", color: .brown, type: .circle)// +// w3wHelper.addMarker(at: "legend.milkman.upholding", color: .blue, type: .circle)// +// w3wHelper.addMarker(at: "unsecured.slugs.unveils", color: .secondary, type: .circle)// +// w3wHelper.addMarker(at: "towers.oiled.dentures", color: .yellow, type: .circle)// +// w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .purple, type: .circle)// +// w3wHelper.addMarker(at: "gladiators.surface.eyeliner", color: .purple, type: .circle)// +// w3wHelper.addMarker(at: "attaching.things.global", color: .purple, type: .circle)// +// +// w3wHelper.addMarker(at: "nipped.concluded.fabric", color: .w3wErrorBase, type: .circle)// +// w3wHelper.addMarker(at: "slept.beakers.amuses", color: .brown, type: .circle)// +// +// w3wHelper.addMarker(at: "deflation.dollar.purple", color: .darkBlue , type: .circle)// +// w3wHelper.addMarker(at: "croutons.approve.drew", color: .red , type: .circle)// +// w3wHelper.addMarker(at: "musically.storms.smirks", color: .darkBlueAlpha60, type: .circle)// +// w3wHelper.addMarker(at: "dissolves.discloses.voting", color: .w3wSuccessLabelDark, type: .circle)// +// w3wHelper.addMarker(at: "trading.yachting.wrong", color: .mustard, type: .circle)// + + + let coordinate = CLLocationCoordinate2D( + latitude: 10.780468, + longitude: 106.705438 + ) + + let span = MKCoordinateSpan( + latitudeDelta: 0.0002, + longitudeDelta: 0.0002 + ) + + let region = MKCoordinateRegion( + center: coordinate, + span: span + ) + // self.setCenter(coordinate, animated: true) + // self.setRegion(region, animated: true) + w3wHelper.setRegion(region, animated: true) + } + + func attachTapRecognizer() { + + let mapView = w3wHelper.mapView + + let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) + tap.numberOfTapsRequired = 1 + tap.numberOfTouchesRequired = 1 + + let doubleTap = UITapGestureRecognizer(target: self, action:nil) + doubleTap.numberOfTapsRequired = 2 + mapView?.addGestureRecognizer(doubleTap) + tap.require(toFail: doubleTap) + + + tap.delegate = self + + mapView?.addGestureRecognizer(tap) + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if isNotPartofTheMap(view: touch.view) { + return false + } else { + return true + } + } + + + func isNotPartofTheMap(view: UIView?) -> Bool { + if view == nil { + return false + } else if view is MKAnnotationView || view is UIButton { + return true + } else { + return isNotPartofTheMap(view: view?.superview) + } + } + + @objc func tapped(_ gestureRecognizer : UITapGestureRecognizer) { + let mapView = w3wHelper.mapView + let location = gestureRecognizer.location(in: mapView) + + if let coordinates = mapView?.convert(location, toCoordinateFrom: mapView) { + self.w3wHelper.select(at: coordinates) { [weak self] result in + + guard let self = self else { return } + + switch result { + + case .success(let square): + + //build the list + let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) + markersList.add(listName: "favorites", color: .w3wBrandBase) + markersList.add(square: square, listName: "favorites") + // self.viewModel.mapState.markers.send(markersList) + + self.viewModel.mapState.selected.send(square) + case .failure(let error): + print("Show Error") + + default: break + + } + + + } + + } + } + +} + +extension W3WAppleMapView: MKMapViewDelegate { + + // MARK: UIMapViewDelegates + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + let currentMapScale = W3WMapScale(span: mapView.region.span, mapSize: mapView.frame.size) + + if currentMapScale.value > transitionScale.value { + //update map + } + print("zoomlevel - mapscale: \(self.zoomLevel) \(currentMapScale.value)") + w3wHelper.mapViewDidChangeVisibleRegion(mapView) + viewModel.output.send(.camera(getCameraState())) + + } + + public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { + + if let w3wOverlay = w3wHelper.mapRenderer(overlay: overlay) { + return w3wOverlay + } + return MKOverlayRenderer() + + } + + /// delegate callback to provide a cusomt annotation view + public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { + return w3wHelper.mapView(mapView, viewFor: annotation, with: self.transitionScale.value) + } + + public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { + w3wHelper.mapView(mapView, didAdd: renderers) + } + + public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { + w3wHelper.mapView(mapView, didAdd: views) + } + + public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + w3wHelper.mapView(mapView, regionWillChangeAnimated: animated) + } + + public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + + //test for zoomslevel + let mapRect = mapView.visibleMapRect + let mapWidthInPoints = mapView.frame.size.width + let zoomScale = mapRect.size.width / Double(mapWidthInPoints) + + + w3wHelper.mapView(mapView, regionDidChangeAnimated: animated) + + } + + public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { + + } + + //when marker is being selected + public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + if let markerView = view.annotation as? W3WAppleMapAnnotation { + + } + + w3wHelper.mapView(mapView, didSelect: view) + } + +} + +extension W3WAppleMapView { + +} + diff --git a/Sources/W3WSwiftComponentsMapApple/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/W3WAppleMapView.swift deleted file mode 100644 index b3245b9..0000000 --- a/Sources/W3WSwiftComponentsMapApple/W3WAppleMapView.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// W3WAppleMapView.swift -// -// -// Created on 03/12/2024. -// - -import MapKit -import W3WSwiftCore -import W3WSwiftComponentsMap - - -/// An Apple Map Kit Map -public class W3WAppleMapView: MKMapView, W3WMapViewProtocol, W3WEventSubscriberProtocol, MKMapViewDelegate { - public var subscriptions = W3WEventsSubscriptions() - - /// The map view model to use - public var viewModel: W3WMapViewModelProtocol - - /// The available map types - public var types: [W3WMapType] { get { return [.standard, .satellite, .hybrid, "silly"] } } - - - /// Make an Apple Map Kit Map - /// - Parameters - /// - viewModel: The viewModel to use - public init(viewModel: W3WMapViewModelProtocol) { - self.viewModel = viewModel - super.init(frame: .w3wWhatever) - } - - - /// Make an Apple Map Kit Map - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - /// Change the viewModel for this map. Typically used when - /// switching maps in the map view - /// - Parameters - /// - viewModel: The viewModel to use - public func set(viewModel: W3WMapViewModelProtocol) { - self.viewModel = viewModel - } - - - /// Change the map type, there is a convenince function in W3WMapViewProtocol - /// that accepts a W3WMapType, and calls this. Common map types are defined - /// there - /// - Parameters - /// - type: A string type from the array `self.types` - public func set(type: String) { - } - -} From 2ae663e53470aeb3c4da51170ab0e8f587fcbc9e Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Mon, 24 Mar 2025 18:51:18 +0700 Subject: [PATCH 02/20] Update 'selectsquare()' behavior --- .../Drawing/Grid/W3WAppleMapGridDrawing.swift | 2 +- .../Helper/W3WAppleMapHelper.swift | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift index aafbfec..c7abd8d 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift @@ -493,7 +493,7 @@ extension W3WAppleMapGridDrawingProtocol { if case .square = annotation.type { pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color)) .get(size: W3WIconSize(value: CGSize(width: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0 , height: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0))) - centerOffset = CGPoint(x: 0.0, y: (-10)) + centerOffset = CGPoint(x: 0.0, y: (-5)) } diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 5600663..6c6e1f3 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -284,9 +284,8 @@ public extension W3WAppleMapHelper { let squareSize = getPointsPerSquare() if let selectedSquare = selectedSquare { - //annotations - if squareSize > self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { - + // if(annotations.count != 0 && squares?.count == 0) { + if squareSize < self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square let previousBoxId = selectedSquare.bounds?.id From fdcdeb3043dab863c9e318031481879f91df83b6 Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Wed, 26 Mar 2025 14:45:19 +0700 Subject: [PATCH 03/20] Update size of pins --- .../Drawing/Grid/W3WAppleMapGridDrawing.swift | 324 +++++++++++------- .../Helper/W3WAppleMapHelper.swift | 109 +++--- .../Type/W3WAppleMapGridData.swift | 60 ++-- .../View/W3WAppleMapView.swift | 73 ++-- 4 files changed, 321 insertions(+), 245 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift index c7abd8d..afb574b 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift @@ -42,7 +42,7 @@ extension W3WAppleMapGridDrawingProtocol { if let lastZoomPointsPerSquare = mapGridData?.lastZoomPointsPerSquare { let squareSize = getPointsPerSquare() - if (squareSize < CGFloat(12.0) && lastZoomPointsPerSquare > CGFloat(12.0)) || (squareSize > CGFloat(12.0) && lastZoomPointsPerSquare < CGFloat(12.0)) { + if (squareSize < CGFloat(12) && lastZoomPointsPerSquare > CGFloat(12)) || (squareSize > CGFloat(12) && lastZoomPointsPerSquare < CGFloat(12)) { redrawPins() } @@ -175,7 +175,8 @@ extension W3WAppleMapGridDrawingProtocol { } mapGridData.squareRenderer = squareRenderer - return squareRenderer + + return mapGridData.squareRenderer } return nil @@ -191,11 +192,38 @@ extension W3WAppleMapGridDrawingProtocol { } + func getPointsPerMeter() -> CGFloat { + let mapCenter = mapView!.centerCoordinate + + // Create two points 1 meter apart (heading east) + let metersPerDegreeAtEquator = 111319.9 // meters per degree of longitude at the equator + let metersPerDegree = metersPerDegreeAtEquator * cos(mapCenter.latitude * .pi / 180.0) + let longitudeDelta = 1.0 / metersPerDegree + + let point1 = mapCenter + let point2 = CLLocationCoordinate2D( + latitude: mapCenter.latitude, + longitude: mapCenter.longitude + longitudeDelta + ) + + // Convert both points to screen coordinates + let point1Screen = mapView!.convert(point1, toPointTo: nil) + let point2Screen = mapView!.convert(point2, toPointTo: nil) + + // Calculate the distance in points + let distance = hypot(point2Screen.x - point1Screen.x, point2Screen.y - point1Screen.y) + + return distance + } + func getPointsPerSquare() -> CGFloat { let threeMeterMapSquare = MKCoordinateRegion(center: mapView!.centerCoordinate, latitudinalMeters: 3, longitudinalMeters: 3); let threeMeterViewSquare = mapView!.convert(threeMeterMapSquare, toRectTo: nil) return threeMeterViewSquare.size.height + + // let pointsPerMeter = getPointsPerMeter() + // return pointsPerMeter * 3 // For a 3-meter square } func redrawAll() { @@ -228,8 +256,8 @@ extension W3WAppleMapGridDrawingProtocol { var alpha = CGFloat(0.0) let pointsPerSquare = self.getPointsPerSquare() - if pointsPerSquare > CGFloat(11.0) { - alpha = (pointsPerSquare - CGFloat(11.0)) / CGFloat(11.001) + if pointsPerSquare > CGFloat(12) { + alpha = (pointsPerSquare - CGFloat(12)) / CGFloat(11.001) //001 } if alpha > 1.0 { @@ -257,8 +285,7 @@ extension W3WAppleMapGridDrawingProtocol { showOutline(square, a) } //return an empty box - let box = MKAnnotationView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) - return box + return MKAnnotationView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) } else { if let square = a.square { hideOutline(square) @@ -306,105 +333,113 @@ extension W3WAppleMapGridDrawingProtocol { } } + /// makes overlays from the squares func updateSquares() { - guard let gridData = self.mapGridData else { return } - - // Create a dispatch group to coordinate synchronization - let group = DispatchGroup() - - var colorsCopy = [Int64: W3WColor]() - - // Enter the group before starting work - group.enter() - - // Get colors from main thread - W3WThread.runOnMain { - colorsCopy = gridData.overlayColors - group.leave() // Signal that colors are ready - } - - // Wait for colors to be copied - group.wait() - - // Initialize the hash tracking variable if needed - if gridData.previousStateHash == nil { - gridData.previousStateHash = 0 - } - - // Create a comprehensive hash of the entire rendering state - var stateHasher = Hasher() - - // Hash the square IDs - for square in gridData.squares ?? [] { - if let id = square.bounds?.id { - stateHasher.combine(id) - } - } - - // Hash the colors - for (id, color) in colorsCopy { - stateHasher.combine(id) - stateHasher.combine(color.description) - } - // Hash the selected square - if let selectedId = gridData.selectedSquare?.bounds?.id { - stateHasher.combine(selectedId) - } - - // Hash the markers commented out in your implementation - // for marker in gridData.markers { - // if let id = marker.bounds?.id { - // stateHasher.combine(id) - // } - // } - - let currentStateHash = stateHasher.finalize() - - // If nothing has changed, skip the update - if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 { - return - } - - // Update hash for next comparison - gridData.previousStateHash = currentStateHash - - var boxes = [(polyline: W3WMapSquareLines, color: W3WColor?)]() - - for square in gridData.squares ?? [] { - if let ne = square.bounds?.northEast, - let sw = square.bounds?.southWest { + guard let gridData = self.mapGridData else { return } + + // Create a dispatch group to coordinate synchronization + let group = DispatchGroup() + + var colorsCopy = [Int64: W3WColor]() + + // Enter the group before starting work + group.enter() + + // Get colors from main thread + W3WThread.runOnMain { + colorsCopy = gridData.overlayColors + group.leave() // Signal that colors are ready + } + + // Wait for colors to be copied + group.wait() + + // Initialize the hash tracking variable if needed + if gridData.previousStateHash == nil { + gridData.previousStateHash = 0 + } + + // Create a comprehensive hash of the entire rendering state + var stateHasher = Hasher() + + // Hash the square IDs + for square in gridData.squares ?? [] { + + if let id = square.bounds?.id { - let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) - let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) - let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) + if (square.bounds?.id == gridData.selectedSquare?.bounds?.id) { + + } + stateHasher.combine(id) + } + } + + // Hash the colors + for (id, color) in colorsCopy { + stateHasher.combine(id) + stateHasher.combine(color.description) + } - polyline.associatedSquare = square - - let boxId = polyline.box.id ?? 0 - let color = colorsCopy[boxId] - - boxes.append((polyline: polyline, color: color)) - } - } - + // Hash the selected square + if let selectedId = gridData.selectedSquare?.bounds?.id { + stateHasher.combine(selectedId) + } + + // Hash the markers commented out in your implementation + // for marker in gridData.markers { + // if let id = marker.bounds?.id { + // stateHasher.combine(id) + // } + // } + + let currentStateHash = stateHasher.finalize() + + // If nothing has changed, skip the update + if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 { + return + } + + // Update hash for next comparison + gridData.previousStateHash = currentStateHash + + var boxes = [(polyline: W3WMapSquareLines, color: W3WColor?)]() + + for square in gridData.squares ?? [] { + if let ne = square.bounds?.northEast, + let sw = square.bounds?.southWest { + + let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) + let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) + let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) + + // polyline.associatedSquare = square + + let boxId = polyline.box.id ?? 0 + let color = colorsCopy[boxId] + + boxes.append((polyline: polyline, color: color)) + } + } + W3WThread.runOnMain { if !gridData.coloredPolylines.isEmpty { - gridData.coloredPolylines.removeAll() + gridData.coloredPolylines = [] } - - // Create a local copy of boxes to ensure thread safety - let boxesCopy = boxes + } + + // Create a local copy of boxes to ensure thread safety + let boxesCopy = boxes - W3WThread.runOnMain { - self.removeSquareOverlays() - // Use boxesCopy instead of the original boxes array - for box in boxesCopy { - self.addOverlay(box.polyline, box.color) - } - } - } + W3WThread.runOnMain { + self.removeSquareOverlays() + // Use boxesCopy instead of the original boxes array + for box in boxesCopy { + addOverlay(box.polyline, box.color) + } + } + } func updateSelectedSquare() { guard let gridData = self.mapGridData else { return } @@ -480,28 +515,49 @@ extension W3WAppleMapGridDrawingProtocol { let identifier = "w3wPin" let color : W3WColor? = annotation.color var pinImage: UIImage? - let pinSize = CGFloat(40.0) var centerOffset: CGPoint = .zero - let s = annotation.square - let w = s?.words + let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier) + if case .circle = annotation.type { - pinImage = W3WImage(drawing: .mapCircle, colors: .standardMaps.with(background: color)).get(size: W3WIconSize(value: CGSize(width: mapGridData?.pinWidth ?? CGFloat(30.0) , height: mapGridData?.pinHeight ?? CGFloat(30.0)))) + + let circleWidth = mapGridData?.pinWidth ?? CGFloat(30.0) + let circleHeight = mapGridData?.pinHeight ?? CGFloat(30.0) + let circleFrameSize = mapGridData?.pinFrameSize ?? CGFloat(30.0) + + pinImage = W3WImage(drawing: .mapCircle, colors: .standardMaps.with(background: color)).get(size: W3WIconSize(value: CGSize(width: circleWidth , height: circleHeight))) + centerOffset = CGPoint(x: 0.0, y: 0.0) + annotationView.image = pinImage + annotationView.frame.size = CGSize(width: circleFrameSize, height: circleFrameSize) + + if #available(iOS 14.0, *) { + annotationView.zPriority = .defaultUnselected + } } if case .square = annotation.type { + + let squareSize = mapGridData?.pinSquareSize ?? CGFloat(50.0) + let squareWidth = squareSize / 2.0 + let squareHeight = squareSize / 2.0 + pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color)) - .get(size: W3WIconSize(value: CGSize(width: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0 , height: (mapGridData?.pinSize ?? CGFloat(40.0)) / 2.0))) - centerOffset = CGPoint(x: 0.0, y: (-5)) + .get(size: W3WIconSize(value: CGSize(width: squareWidth , height: squareHeight))) + + centerOffset = CGPoint(x: 0.0, y: (-20.0)) + annotationView.image = pinImage + annotationView.frame.size = CGSize(width: squareSize, height: squareSize) + + if #available(iOS 14.0, *) { + annotationView.zPriority = .max + } + } - - - let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier) - annotationView.image = pinImage - annotationView.frame.size = CGSize(width:mapGridData?.pinWidth ?? CGFloat(35), height: mapGridData?.pinHeight ?? CGFloat(35)) - annotationView.centerOffset = centerOffset - + annotationView.centerOffset = centerOffset + // Make the pin selectable + annotationView.canShowCallout = true + annotationView.isEnabled = true return annotationView } } @@ -889,31 +945,53 @@ extension W3WAppleMapGridDrawingProtocol { } } + func __hideOutline(_ square: W3WSquare) { + // Capture necessary data before async execution + guard let squareId = square.bounds?.id else { + print("Cannot hide square: missing bounds ID") + return + } + + W3WThread.runInBackground { + if let mapGridData = self.mapGridData, + !mapGridData.squares.isEmpty { + return + } + + // Instead of removing in-place, create a new filtered array + let filteredSquares = mapGridData?.squares.filter { square in + return square.bounds?.id != squareId + } + + // Update on main thread + W3WThread.runOnMain { + self.mapGridData?.squares = filteredSquares ?? [] + self.updateSquares() + } + } + } + func hideOutline(_ square: W3WSquare) { W3WThread.runInBackground { - if var squares = self.mapGridData?.squares { - squares.removeAll(where: { s in - return s.bounds?.id == square.bounds?.id - }) + if let s = self.mapGridData?.squares { + if s != nil { + W3WThread.runOnMain { + self.mapGridData?.squares.removeAll(where: { s in + return s.bounds?.id == square.bounds?.id + }) + } + } } - -// for anno in annotations { -// if let a = anno as? W3WAppleMapAnnotation { -// print(a.square?.words) -// print(annotations.count) -// } -// } - self.updateSquares() + } - } func hideOutlineMarker(_ square: W3WSquare) { W3WThread.runInBackground { if let s = self.mapGridData?.markers { - if s.count != 0 { + if s != nil { self.mapGridData?.markers.removeAll(where: { m in return m.bounds?.id == square.bounds?.id }) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 6c6e1f3..9d56589 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -75,13 +75,13 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WApp func setGridColor() { if let gridData = mapGridData { - gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) + gridData.mapGridColor.send(mapType == .standard ? .red : .white) } } func setGridLine() { if let gridData = mapGridData { - gridData.mapGridLineThickness.send(2.0) + gridData.mapGridLineThickness.send(4.0) } } @@ -197,7 +197,55 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WApp } public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { - + // Maintain a dictionary of positions by section + var positionsBySection = [String: [CGPoint]]() + + // Minimum distance between pins in points + let minDistance: CGFloat = 5.0 + + for view in views { + guard let annotation = view.annotation as? W3WAppleMapAnnotation else { continue } + + // Create a section key based on annotation's location + // Round to nearest grid to group nearby pins + let gridSize: Double = 0.0001 // Adjust based on your needs + let latSection = Int((annotation.square?.coordinates!.latitude ?? 0) / gridSize) + let lngSection = Int(annotation.square?.coordinates!.longitude ?? 0 / gridSize) + let sectionKey = "\(latSection)_\(lngSection)" + + // Get current center point for this view + let center = view.center + + // Get existing positions in this section + var positions = positionsBySection[sectionKey] ?? [] + + // Check if this position is too close to existing ones + var needsAdjustment = false + for existingPos in positions { + let distance = hypot(center.x - existingPos.x, center.y - existingPos.y) + if distance < minDistance { + needsAdjustment = true + break + } + } + + // If too close, apply a small offset + if needsAdjustment { + // Calculate offset direction (try to avoid overlaps) + let offsetX = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + let offsetY = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + + // Apply offset + view.centerOffset = CGPoint( + x: view.centerOffset.x + offsetX, + y: view.centerOffset.y + offsetY + ) + } + + // Add this position to our tracking dictionary + positions.append(view.center) + positionsBySection[sectionKey] = positions + } } } @@ -232,42 +280,10 @@ public extension W3WAppleMapHelper { } func select(at: W3WSquare) { - createMarkerForCondition(at: at) - - //sample code to update markers - // let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) - // markersList.add(listName: "favorites", color: .w3wBrandBase) - // markersList.add(square: at, listName: "favorites") - - // self.mapGridData?.savedList = markersList - - // completion(markersList) - ///Sample code to add - -// var randomColors: [W3WColor] = [] -// -// for _ in 0..<10 { -// let red = CGFloat.random(in: 0...1) -// let green = CGFloat.random(in: 0...1) -// let blue = CGFloat.random(in: 0...1) -// let uiColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0) -// let randomColor = W3WColor(uiColor: uiColor) -// -// randomColors.append(randomColor) -// } -// -// let randomIndex = Int.random(in: 0.. W3WAppleMapAnnotation? { - for annotation in annotations { - if let a = annotation as? W3WAppleMapAnnotation { - if (a.square?.bounds?.id == square?.bounds?.id) { - return a - } - } - } - - return nil - } -} diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift index 8a15082..d5d3a0b 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -19,29 +19,29 @@ public class W3WAppleMapGridData { private var cancellables = Set() - public let squareColor = W3WLive(.w3wBrandBase) + let squareColor = W3WLive(.w3wBrandBase) - public let mapGridColor = W3WLive(.mediumGrey) - public let mapGridLineThickness = W3WLive(0.5) + let mapGridColor = W3WLive(.mediumGrey) + let mapGridLineThickness = W3WLive(0.5) - public let mapSquareColor = W3WLive(.black) - public let mapSquareLineThickness = W3WLive(0.1) + let mapSquareColor = W3WLive(.black) + let mapSquareLineThickness = W3WLive(0.1) - public let selectedSquareBorderColor = W3WLive(.black) - public let selectedSquareThickness = W3WLive(0.5) + let selectedSquareBorderColor = W3WLive(.black) + let selectedSquareThickness = W3WLive(0.5) + + let pinWidth = CGFloat(30.0) + let pinHeight = CGFloat(30.0) + let pinFrameSize = CGFloat(30.0) + let pinSquareSize = CGFloat(50.0) + let squarePinFrameSize = CGFloat(50.0) - public let pinWidth: CGFloat = CGFloat(35.0) - public let pinHeight: CGFloat = CGFloat(35.0) - public let pinSize = CGFloat(40.0) public var onError: W3WMapErrorHandler = { _ in } var gridRendererPointer: W3WMapGridRenderer? = nil var squareRendererPointer: W3WMapSquaresRenderer? = nil - var gridLinePointer: W3WMapGridLines? = nil - - var w3w: W3WProtocolV4? /// language to use currently @@ -60,18 +60,25 @@ public class W3WAppleMapGridData { var currentSquare: W3WSquare? = nil - public var currentOverlays: [Int64: MKOverlay] = [:] + var currentOverlays: [Int64: MKOverlay] = [:] + + var overlayColors: [Int64: W3WColor] = [:] + + var previousSquareIds = Set() + + var previousSquareIdsHash: Int? + + var previousStateHash: Int? - public var overlayColors: [Int64: W3WColor] = [:] + var coloredPolylines = [ColoredPolyline]() - public var previousSquareIds = Set() - public var previousSquareIdsHash: Int? - public var previousStateHash: Int? - public var coloredPolylines = [ColoredPolyline]() + ///keep track of annoptation positions globally + var annotationPositions: [String: CGPoint] = [:] - public var scheme: W3WScheme? = .w3w + var scheme: W3WScheme? = .w3w var mapZoomLevel = CGFloat(0.0) + var pointsPerSquare = CGFloat(12.0) /// keep track of the zoom level so we can change pins to squares at a certain point @@ -160,10 +167,9 @@ public class ColoredPolyline { } - public class W3WMapSquareLines: MKPolyline { - var associatedSquare: W3WSquare? +// var associatedSquare: W3WSquare? var box: W3WBaseBox { let points = self.points() @@ -172,7 +178,7 @@ public class W3WMapSquareLines: MKPolyline { return W3WBaseBox(southWest: sw, northEast: ne) } - convenience init? (bounds: W3WBaseBox?, square: W3WSquare? = nil) { + convenience init? (bounds: W3WBaseBox?) { guard let ne = bounds?.northEast, let sw = bounds?.southWest else { return nil @@ -183,14 +189,13 @@ public class W3WMapSquareLines: MKPolyline { let coordinates = [nw, ne, se, sw, nw] self.init(coordinates: coordinates, count: 5) - self.associatedSquare = square + // self.associatedSquare = square } } public class W3WMapSquaresRenderer: MKPolylineRenderer { private var squareW3WImage: UIImage? - // Cache the bounding rect to avoid recalculating it private var cachedBoundingRect: CGRect? @@ -202,7 +207,6 @@ public class W3WMapSquaresRenderer: MKPolylineRenderer { return } - // Calculate the display rect if needed let displayRect = cachedBoundingRect ?? { let rect = self.rect(for: self.polyline.boundingMapRect) @@ -221,14 +225,10 @@ public class W3WMapSquaresRenderer: MKPolylineRenderer { // Method to set the W3WImage public func setSquareImage(_ w3wImage: UIImage?) { - self.squareW3WImage = w3wImage self.cachedBoundingRect = nil // Invalidate cached rect self.setNeedsDisplay() } - - - } #endif diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index e94c46e..c3acc27 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -157,41 +157,41 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .w3wBrandBase, type: .circle)// w3wHelper.addMarker(at: "outgrown.onions.marathon", color: .brown, type: .circle)// -// w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// -// w3wHelper.addMarker(at: "dugouts.flaking.pleasing", color: .brightGreen, type: .circle)// -// w3wHelper.addMarker(at: "humans.stags.insulated", color: .darkGreen, type: .circle) // -// w3wHelper.addMarker(at: "melts.luggage.cuter", color: .systemPurple, type: .circle) //// -// w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle) -// w3wHelper.addMarker(at: "bleaching.urgent.heartache", color: .aqua, type: .circle)// -// w3wHelper.addMarker(at: "allowable.realm.crafted", color: .lightCyan, type: .circle)// -// w3wHelper.addMarker(at: "admire.couch.celebrate", color: .aqua, type: .circle)// -// w3wHelper.addMarker(at: "listen.searching.reviews", color: .charcoal, type: .circle)// -// w3wHelper.addMarker(at: "vaulting.lasts.birthing", color: .dullRed, type: .circle)// -// w3wHelper.addMarker(at: "sleeping.beanbag.contour", color: .darkBlue , type: .circle)// -// w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle)// -// w3wHelper.addMarker(at: "often.opts.blushes", color: .darkBlueAlpha60, type: .circle)// -// w3wHelper.addMarker(at: "skinny.bound.reclusive", color: .w3wSuccessLabelDark, type: .circle)// -// w3wHelper.addMarker(at: "segregate.unwraps.majors", color: .mustard, type: .circle)// -// w3wHelper.addMarker(at: "losses.pheasants.eagle", color: .systemYellow, type: .circle) -// w3wHelper.addMarker(at: "protected.nylon.will", color: .w3wErrorBase, type: .circle)// -// w3wHelper.addMarker(at: "majors.supplier.playing", color: .brown, type: .circle)// -// w3wHelper.addMarker(at: "legend.milkman.upholding", color: .blue, type: .circle)// -// w3wHelper.addMarker(at: "unsecured.slugs.unveils", color: .secondary, type: .circle)// -// w3wHelper.addMarker(at: "towers.oiled.dentures", color: .yellow, type: .circle)// -// w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .purple, type: .circle)// -// w3wHelper.addMarker(at: "gladiators.surface.eyeliner", color: .purple, type: .circle)// -// w3wHelper.addMarker(at: "attaching.things.global", color: .purple, type: .circle)// -// -// w3wHelper.addMarker(at: "nipped.concluded.fabric", color: .w3wErrorBase, type: .circle)// -// w3wHelper.addMarker(at: "slept.beakers.amuses", color: .brown, type: .circle)// -// -// w3wHelper.addMarker(at: "deflation.dollar.purple", color: .darkBlue , type: .circle)// -// w3wHelper.addMarker(at: "croutons.approve.drew", color: .red , type: .circle)// -// w3wHelper.addMarker(at: "musically.storms.smirks", color: .darkBlueAlpha60, type: .circle)// -// w3wHelper.addMarker(at: "dissolves.discloses.voting", color: .w3wSuccessLabelDark, type: .circle)// -// w3wHelper.addMarker(at: "trading.yachting.wrong", color: .mustard, type: .circle)// - - + // w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// + w3wHelper.addMarker(at: "dugouts.flaking.pleasing", color: .brightGreen, type: .circle)// + w3wHelper.addMarker(at: "humans.stags.insulated", color: .darkGreen, type: .circle) // + w3wHelper.addMarker(at: "melts.luggage.cuter", color: .systemPurple, type: .circle) //// + w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle) + w3wHelper.addMarker(at: "bleaching.urgent.heartache", color: .aqua, type: .circle)// + w3wHelper.addMarker(at: "allowable.realm.crafted", color: .lightCyan, type: .circle)// + w3wHelper.addMarker(at: "admire.couch.celebrate", color: .aqua, type: .circle)// + w3wHelper.addMarker(at: "listen.searching.reviews", color: .charcoal, type: .circle)// + w3wHelper.addMarker(at: "vaulting.lasts.birthing", color: .dullRed, type: .circle)// + w3wHelper.addMarker(at: "sleeping.beanbag.contour", color: .darkBlue , type: .circle)// + w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle)// + w3wHelper.addMarker(at: "often.opts.blushes", color: .darkBlueAlpha60, type: .circle)// + w3wHelper.addMarker(at: "skinny.bound.reclusive", color: .w3wSuccessLabelDark, type: .circle)// + w3wHelper.addMarker(at: "segregate.unwraps.majors", color: .mustard, type: .circle)// + w3wHelper.addMarker(at: "losses.pheasants.eagle", color: .systemYellow, type: .circle) + w3wHelper.addMarker(at: "protected.nylon.will", color: .w3wErrorBase, type: .circle)// + w3wHelper.addMarker(at: "majors.supplier.playing", color: .brown, type: .circle)// + w3wHelper.addMarker(at: "legend.milkman.upholding", color: .blue, type: .circle)// + w3wHelper.addMarker(at: "unsecured.slugs.unveils", color: .secondary, type: .circle)// + w3wHelper.addMarker(at: "towers.oiled.dentures", color: .yellow, type: .circle)// + w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .purple, type: .circle)// + w3wHelper.addMarker(at: "gladiators.surface.eyeliner", color: .purple, type: .circle)// + w3wHelper.addMarker(at: "attaching.things.global", color: .purple, type: .circle)// + + w3wHelper.addMarker(at: "nipped.concluded.fabric", color: .w3wErrorBase, type: .circle)// + w3wHelper.addMarker(at: "slept.beakers.amuses", color: .brown, type: .circle)// + + w3wHelper.addMarker(at: "deflation.dollar.purple", color: .darkBlue , type: .circle)// + w3wHelper.addMarker(at: "croutons.approve.drew", color: .red , type: .circle)// + w3wHelper.addMarker(at: "musically.storms.smirks", color: .darkBlueAlpha60, type: .circle)// + w3wHelper.addMarker(at: "dissolves.discloses.voting", color: .w3wSuccessLabelDark, type: .circle)// + w3wHelper.addMarker(at: "trading.yachting.wrong", color: .mustard, type: .circle)// + + w3wHelper.addMarker(at: "convert.universal.subject", color: .mustard, type: .circle)// let coordinate = CLLocationCoordinate2D( latitude: 10.780468, longitude: 106.705438 @@ -293,10 +293,9 @@ extension W3WAppleMapView: MKMapViewDelegate { if currentMapScale.value > transitionScale.value { //update map } - print("zoomlevel - mapscale: \(self.zoomLevel) \(currentMapScale.value)") w3wHelper.mapViewDidChangeVisibleRegion(mapView) viewModel.output.send(.camera(getCameraState())) - + } public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { From a22f6d2940d95a796b3e5764012928a5a5fac39c Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Mon, 14 Apr 2025 14:52:38 +0700 Subject: [PATCH 04/20] update mapState to viewModel.input --- Package.swift | 3 ++- .../Helper/W3WAppleMapHelper.swift | 13 ++++++++----- .../View/W3WAppleMapView.swift | 14 +++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Package.swift b/Package.swift index fa6c89b..122c083 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), - .package(path: "../w3w-swift-components-map"), + // .package(path: "../w3w-swift-components-map"), + .package(url: "https://github.com/what3words/w3w-swift-components-map.git", branch: "staging"), .package(url: "https://github.com/what3words/w3w-swift-core.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-wrapper", branch: "master") ], diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 9d56589..4707808 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -208,10 +208,13 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WApp // Create a section key based on annotation's location // Round to nearest grid to group nearby pins - let gridSize: Double = 0.0001 // Adjust based on your needs - let latSection = Int((annotation.square?.coordinates!.latitude ?? 0) / gridSize) - let lngSection = Int(annotation.square?.coordinates!.longitude ?? 0 / gridSize) - let sectionKey = "\(latSection)_\(lngSection)" + let gridSize: Double = 0.0001 // Adjust based on your needs + let latValue: Double = annotation.square?.coordinates?.latitude ?? annotation.coordinate.latitude + let lngValue: Double = annotation.square?.coordinates?.longitude ?? annotation.coordinate.longitude + + let latSection = Int(latValue / gridSize) + let lngSection = Int(lngValue / gridSize) + let sectionKey = "\(latSection)_\(lngSection)" // Get current center point for this view let center = view.center @@ -281,7 +284,7 @@ public extension W3WAppleMapHelper { func select(at: W3WSquare) { createMarkerForConditions(at: at) - } + } func createMarkerForConditions(at: W3WSquare) { diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index c3acc27..1193b5e 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -124,24 +124,24 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView } public func updateSavedLists(markers: W3WMarkersLists) { - viewModel.mapState.markers.send(markers) + viewModel.input.markers.send(markers) } func bind() { - subscribe(to: self.viewModel.mapState.markers) { [weak self] markers in + subscribe(to: self.viewModel.input.markers) { [weak self] markers in guard let self = self else { return } w3wHelper.updateMarkers(markers: markers) } - subscribe(to: self.viewModel.mapState.camera) { [weak self] camera in + subscribe(to: self.viewModel.input.camera) { [weak self] camera in guard let self = self else { return } w3wHelper.updateCamera(camera: camera) } - subscribe(to: self.viewModel.mapState.selected) { [weak self] square in + subscribe(to: self.viewModel.input.selected) { [weak self] square in guard let self = self else { return } w3wHelper.updateSquare(square: square) @@ -157,7 +157,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .w3wBrandBase, type: .circle)// w3wHelper.addMarker(at: "outgrown.onions.marathon", color: .brown, type: .circle)// - // w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// + w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// w3wHelper.addMarker(at: "dugouts.flaking.pleasing", color: .brightGreen, type: .circle)// w3wHelper.addMarker(at: "humans.stags.insulated", color: .darkGreen, type: .circle) // w3wHelper.addMarker(at: "melts.luggage.cuter", color: .systemPurple, type: .circle) //// @@ -268,7 +268,8 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView markersList.add(square: square, listName: "favorites") // self.viewModel.mapState.markers.send(markersList) - self.viewModel.mapState.selected.send(square) + // self.viewModel.mapState.selected.send(square) + self.viewModel.output.send(.selected(square)) case .failure(let error): print("Show Error") @@ -276,7 +277,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView } - } } From 03ba64f89f14f00bcbafddb69b81b4f72b2c7494 Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Wed, 16 Apr 2025 18:19:55 +0700 Subject: [PATCH 05/20] fix listening events on selected square --- ...dDrawing.swift => W3WAppleMapDrawer.swift} | 26 +++---- .../Helper/W3WAppleMapHelper.swift | 53 +++++++------- .../Type/W3WImageCache.swift | 2 +- .../View/W3WAppleMapView.swift | 72 +++---------------- 4 files changed, 52 insertions(+), 101 deletions(-) rename Sources/W3WSwiftComponentsMapApple/Drawing/{Grid/W3WAppleMapGridDrawing.swift => W3WAppleMapDrawer.swift} (97%) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift similarity index 97% rename from Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift rename to Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index afb574b..4fd6748 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/Grid/W3WAppleMapGridDrawing.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -15,7 +15,7 @@ import W3WSwiftComponentsMap import Combine -public protocol W3WAppleMapGridDrawingProtocol { +public protocol W3WAppleMapDrawerProtocol { var mapView: MKMapView? { get } var region: MKCoordinateRegion { get } @@ -34,7 +34,7 @@ public protocol W3WAppleMapGridDrawingProtocol { func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) } -extension W3WAppleMapGridDrawingProtocol { +extension W3WAppleMapDrawerProtocol { public func updateMap() { @@ -42,7 +42,7 @@ extension W3WAppleMapGridDrawingProtocol { if let lastZoomPointsPerSquare = mapGridData?.lastZoomPointsPerSquare { let squareSize = getPointsPerSquare() - if (squareSize < CGFloat(12) && lastZoomPointsPerSquare > CGFloat(12)) || (squareSize > CGFloat(12) && lastZoomPointsPerSquare < CGFloat(12)) { + if (squareSize < CGFloat(12.0) && lastZoomPointsPerSquare > CGFloat(12.0)) || (squareSize > CGFloat(12.0) && lastZoomPointsPerSquare < CGFloat(12.0)) { redrawPins() } @@ -150,7 +150,7 @@ extension W3WAppleMapGridDrawingProtocol { } let w3wImage: UIImage? - w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 25, height: 25)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 25, height: 25)) + w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 55, height: 55)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 55, height: 55)) if (isSelectedSquare) { if (isMarker == true) { @@ -272,7 +272,7 @@ extension W3WAppleMapGridDrawingProtocol { } -extension W3WAppleMapGridDrawingProtocol { +extension W3WAppleMapDrawerProtocol { // MARK: Pins / Annotations @@ -525,7 +525,7 @@ extension W3WAppleMapGridDrawingProtocol { let circleHeight = mapGridData?.pinHeight ?? CGFloat(30.0) let circleFrameSize = mapGridData?.pinFrameSize ?? CGFloat(30.0) - pinImage = W3WImage(drawing: .mapCircle, colors: .standardMaps.with(background: color)).get(size: W3WIconSize(value: CGSize(width: circleWidth , height: circleHeight))) + pinImage = W3WImage(drawing: .mapCircle, colors: .standardMaps.with(background: color).with(foreground: color?.complimentaryTextColor())).get(size: W3WIconSize(value: CGSize(width: circleWidth , height: circleHeight))) centerOffset = CGPoint(x: 0.0, y: 0.0) annotationView.image = pinImage @@ -539,11 +539,11 @@ extension W3WAppleMapGridDrawingProtocol { if case .square = annotation.type { let squareSize = mapGridData?.pinSquareSize ?? CGFloat(50.0) - let squareWidth = squareSize / 2.0 - let squareHeight = squareSize / 2.0 + let pinImageWidth = squareSize + let pinImageHeight = squareSize - pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color)) - .get(size: W3WIconSize(value: CGSize(width: squareWidth , height: squareHeight))) + pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color).with(foreground: color?.complimentaryTextColor())) + .get(size: W3WIconSize(value: CGSize(width: pinImageWidth , height: pinImageHeight))) centerOffset = CGPoint(x: 0.0, y: (-20.0)) annotationView.image = pinImage @@ -562,7 +562,7 @@ extension W3WAppleMapGridDrawingProtocol { } } -extension W3WAppleMapGridDrawingProtocol { +extension W3WAppleMapDrawerProtocol { // MARK: SQUARES CHECK @@ -888,7 +888,7 @@ extension W3WAppleMapGridDrawingProtocol { } -extension W3WAppleMapGridDrawingProtocol { +extension W3WAppleMapDrawerProtocol { /// remove a what3words annotation from the map if it is present public func removeMarker(at suggestion: W3WSuggestion?) { @@ -1054,7 +1054,7 @@ extension W3WAppleMapGridDrawingProtocol { } -extension W3WAppleMapGridDrawingProtocol { +extension W3WAppleMapDrawerProtocol { /// set the map center to a coordinate, and set the minimum visible area func set(center: CLLocationCoordinate2D) { diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 4707808..53c679f 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -12,7 +12,7 @@ import W3WSwiftCore import W3WSwiftComponentsMap import W3WSwiftDesign -public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WAppleMapHelperProtocol { +public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMapHelperProtocol { public weak var mapView: MKMapView? @@ -39,9 +39,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WApp return mapView?.mapType as! MKMapType } set { - mapView?.mapType = newValue - self.redrawAll() setGridColor() } @@ -75,7 +73,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapGridDrawingProtocol, W3WApp func setGridColor() { if let gridData = mapGridData { - gridData.mapGridColor.send(mapType == .standard ? .red : .white) + gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) } } @@ -270,7 +268,7 @@ public extension W3WAppleMapHelper { } if let s = square { W3WThread.runOnMain { - self.select(at: s) + //self.select(at: s) completion(.success(s)) } } else { @@ -445,45 +443,50 @@ public extension W3WAppleMapHelper { gridData.squareIsMarker = nil gridData.currentSquare = nil } + redrawPins() + redrawSquares() } public func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { return nil } - - } extension W3WAppleMapHelper { public func updateCamera(camera: W3WMapCamera?) { -// W3WThread.runOnMain { [weak self] in -// if let self = self { -// if let center = camera?.center, let scale = camera?.scale { -// let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) -// mapView?.setRegion(region, animated: true) -// -// } else if let center = camera?.center { -// mapView?.setCenter(center, animated: true) -// -// } else if let scale = camera?.scale { -// let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) -// mapView?.setRegion(region, animated: true) -// } -// } -// } + W3WThread.runOnMain { [weak self] in + if let self = self { + if let center = camera?.center, let scale = camera?.scale { + let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) + mapView?.setRegion(region, animated: true) + + } else if let center = camera?.center { + mapView?.setCenter(center, animated: true) + + } else if let scale = camera?.scale { + let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) + mapView?.setRegion(region, animated: true) + } + } + } } public func updateSquare(square: W3WSquare?) { - + if let square = square { + // addMarker(at: square, color: nil, type: .circle) + self.select(at: square) + } } public func updateMarkers(markers: W3WMarkersLists) { removeAllMarkers() + let _list = markers.getLists() for (_, list) in markers.getLists() { for marker in list.markers { - // addMarker(at: marker, color: list.color, type: .circle) - print(marker.words, list.color) + + addMarker(at: marker, color: list.color, type: .circle) + } } } diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift index f5df15e..bbcd210 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift @@ -21,7 +21,7 @@ class W3WImageCache { } // Get the image - let newImage = W3WImage(drawing: .mapSquare, colors: .standardMaps.with(background: color)) + let newImage = W3WImage(drawing: .mapSquare, colors: .standardMaps.with(background: color) .with(foreground: color.complimentaryTextColor())) .get(size: W3WIconSize(value: size)) // Cache it if it's not nil diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index 1193b5e..34951f7 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -26,7 +26,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView /// The map view model to use public var viewModel: W3WMapViewModelProtocol - var helper: W3WAppleMapGridDrawingProtocol! + var helper: W3WAppleMapDrawerProtocol! typealias W3WHelper = W3WAppleMapHelper @@ -54,7 +54,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView self.viewModel = viewModel super.init(frame: .w3wWhatever) - self.helper = W3WAppleMapHelper(mapView: self, viewModel.w3w) as! any W3WAppleMapGridDrawingProtocol + self.helper = W3WAppleMapHelper(mapView: self, viewModel.w3w) as! any W3WAppleMapDrawerProtocol configure() } @@ -70,13 +70,14 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView bind() tesFuncs() - + + attachTapRecognizer() } func tesFuncs() { - addTestMarkers() - attachTapRecognizer() + // addTestMarkers() + } /// Make an Apple Map Kit Map @@ -131,7 +132,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView subscribe(to: self.viewModel.input.markers) { [weak self] markers in guard let self = self else { return } - w3wHelper.updateMarkers(markers: markers) } @@ -143,7 +143,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView subscribe(to: self.viewModel.input.selected) { [weak self] square in guard let self = self else { return } - w3wHelper.updateSquare(square: square) } @@ -155,43 +154,8 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView func addTestMarkers(){ - w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .w3wBrandBase, type: .circle)// - w3wHelper.addMarker(at: "outgrown.onions.marathon", color: .brown, type: .circle)// - w3wHelper.addMarker(at: "gums.tulip.impulsive", color: .cranberry, type: .circle)// - w3wHelper.addMarker(at: "dugouts.flaking.pleasing", color: .brightGreen, type: .circle)// - w3wHelper.addMarker(at: "humans.stags.insulated", color: .darkGreen, type: .circle) // - w3wHelper.addMarker(at: "melts.luggage.cuter", color: .systemPurple, type: .circle) //// - w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle) - w3wHelper.addMarker(at: "bleaching.urgent.heartache", color: .aqua, type: .circle)// - w3wHelper.addMarker(at: "allowable.realm.crafted", color: .lightCyan, type: .circle)// - w3wHelper.addMarker(at: "admire.couch.celebrate", color: .aqua, type: .circle)// - w3wHelper.addMarker(at: "listen.searching.reviews", color: .charcoal, type: .circle)// - w3wHelper.addMarker(at: "vaulting.lasts.birthing", color: .dullRed, type: .circle)// - w3wHelper.addMarker(at: "sleeping.beanbag.contour", color: .darkBlue , type: .circle)// - w3wHelper.addMarker(at: "described.enforced.prude", color: .red , type: .circle)// - w3wHelper.addMarker(at: "often.opts.blushes", color: .darkBlueAlpha60, type: .circle)// - w3wHelper.addMarker(at: "skinny.bound.reclusive", color: .w3wSuccessLabelDark, type: .circle)// - w3wHelper.addMarker(at: "segregate.unwraps.majors", color: .mustard, type: .circle)// - w3wHelper.addMarker(at: "losses.pheasants.eagle", color: .systemYellow, type: .circle) - w3wHelper.addMarker(at: "protected.nylon.will", color: .w3wErrorBase, type: .circle)// - w3wHelper.addMarker(at: "majors.supplier.playing", color: .brown, type: .circle)// - w3wHelper.addMarker(at: "legend.milkman.upholding", color: .blue, type: .circle)// - w3wHelper.addMarker(at: "unsecured.slugs.unveils", color: .secondary, type: .circle)// - w3wHelper.addMarker(at: "towers.oiled.dentures", color: .yellow, type: .circle)// - w3wHelper.addMarker(at: "intervene.cities.bachelor", color: .purple, type: .circle)// - w3wHelper.addMarker(at: "gladiators.surface.eyeliner", color: .purple, type: .circle)// - w3wHelper.addMarker(at: "attaching.things.global", color: .purple, type: .circle)// - - w3wHelper.addMarker(at: "nipped.concluded.fabric", color: .w3wErrorBase, type: .circle)// - w3wHelper.addMarker(at: "slept.beakers.amuses", color: .brown, type: .circle)// + w3wHelper.addMarker(at: "become.outlooks.rising", color: .white, type: .circle)// - w3wHelper.addMarker(at: "deflation.dollar.purple", color: .darkBlue , type: .circle)// - w3wHelper.addMarker(at: "croutons.approve.drew", color: .red , type: .circle)// - w3wHelper.addMarker(at: "musically.storms.smirks", color: .darkBlueAlpha60, type: .circle)// - w3wHelper.addMarker(at: "dissolves.discloses.voting", color: .w3wSuccessLabelDark, type: .circle)// - w3wHelper.addMarker(at: "trading.yachting.wrong", color: .mustard, type: .circle)// - - w3wHelper.addMarker(at: "convert.universal.subject", color: .mustard, type: .circle)// let coordinate = CLLocationCoordinate2D( latitude: 10.780468, longitude: 106.705438 @@ -257,28 +221,18 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView self.w3wHelper.select(at: coordinates) { [weak self] result in guard let self = self else { return } - switch result { - case .success(let square): - //build the list - let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) - markersList.add(listName: "favorites", color: .w3wBrandBase) - markersList.add(square: square, listName: "favorites") - // self.viewModel.mapState.markers.send(markersList) - - // self.viewModel.mapState.selected.send(square) + // let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) + // markersList.add(listName: "favorites", color: .w3wBrandBase) + // markersList.add(square: square, listName: "favorites") self.viewModel.output.send(.selected(square)) case .failure(let error): print("Show Error") - default: break - } - } - } } @@ -331,7 +285,6 @@ extension W3WAppleMapView: MKMapViewDelegate { let mapWidthInPoints = mapView.frame.size.width let zoomScale = mapRect.size.width / Double(mapWidthInPoints) - w3wHelper.mapView(mapView, regionDidChangeAnimated: animated) } @@ -350,8 +303,3 @@ extension W3WAppleMapView: MKMapViewDelegate { } } - -extension W3WAppleMapView { - -} - From 185becbeb9db7a9e040dd09c461525f414cef46f Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Mon, 5 May 2025 20:13:05 +0700 Subject: [PATCH 06/20] change pin type, remove swift wrapper dependency, fix losing square --- Package.resolved | 44 +- Package.swift | 5 +- .../Drawing/W3WAppleMapDrawer.swift | 469 ++++++++---------- .../Helper/W3WAppleMapHelper.swift | 191 ++++--- .../Type/W3WAppleMapGridData.swift | 55 +- .../Type/W3WColor+.swift | 21 + .../View/W3WAppleMapView.swift | 59 +-- 7 files changed, 398 insertions(+), 446 deletions(-) create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WColor+.swift diff --git a/Package.resolved b/Package.resolved index 3e074da..071e2e1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,22 +1,22 @@ { - "originHash" : "7c1690b067e4e0d940b96b74d150a0946e164fc0e6c3147a12b94185d8604f33", + "originHash" : "290e325529555297e0983cdcd68b712414da1d8a790e623a1eebe2334560ccc6", "pins" : [ { - "identity" : "w3w-swift-components", + "identity" : "w3w-swift-app-events", "kind" : "remoteSourceControl", - "location" : "https://github.com/what3words/w3w-swift-components.git", + "location" : "git@github.com:w3w-internal/w3w-swift-app-events.git", "state" : { - "revision" : "6ed04be4011d86642d1f8b9bbab8ac118bedbd86", - "version" : "3.0.0" + "revision" : "a3aa579afbd1d3b66574cff785a33edbec1c0452", + "version" : "1.1.0" } }, { - "identity" : "w3w-swift-core", + "identity" : "w3w-swift-components-map", "kind" : "remoteSourceControl", - "location" : "https://github.com/what3words/w3w-swift-core.git", + "location" : "https://github.com/what3words/w3w-swift-components-map.git", "state" : { - "revision" : "5e81cb99e3424d0de58e5c3df12716b834a44685", - "version" : "1.1.3" + "branch" : "staging", + "revision" : "9823e608625044c42776a11b745c0ffc7b018021" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-design.git", "state" : { - "revision" : "ce6b9c951d1526915d484c7d723298a0ea3654ca", - "version" : "1.0.8" + "branch" : "staging", + "revision" : "d9087dd0079917d3cf52c96208b15bbbfc306748" } }, { @@ -33,26 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-themes.git", "state" : { - "revision" : "718555fc83d5745724a222c6ba39add74e0d345e", - "version" : "1.2.2" - } - }, - { - "identity" : "w3w-swift-voice-api", - "kind" : "remoteSourceControl", - "location" : "https://github.com/what3words/w3w-swift-voice-api.git", - "state" : { - "revision" : "fa5849ae926e1b5f85198ca3d4d52ae2c9446cd5", - "version" : "1.0.0" - } - }, - { - "identity" : "w3w-swift-wrapper", - "kind" : "remoteSourceControl", - "location" : "https://github.com/what3words/w3w-swift-wrapper", - "state" : { - "branch" : "master", - "revision" : "3c01152aa316b7a9714f038b34a8adb4adeb7c4a" + "branch" : "staging", + "revision" : "991792aa333e57482650216990272152faaeb820" } } ], diff --git a/Package.swift b/Package.swift index 122c083..0220ce0 100644 --- a/Package.swift +++ b/Package.swift @@ -15,10 +15,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), - // .package(path: "../w3w-swift-components-map"), .package(url: "https://github.com/what3words/w3w-swift-components-map.git", branch: "staging"), - .package(url: "https://github.com/what3words/w3w-swift-core.git", "1.0.0"..<"2.0.0"), - .package(url: "https://github.com/what3words/w3w-swift-wrapper", branch: "master") + .package(path: "../w3w-swift-core"), ], targets: [ @@ -31,7 +29,6 @@ let package = Package( .product(name: "W3WSwiftDesign", package: "w3w-swift-design"), .product(name: "W3WSwiftComponentsMap", package: "w3w-swift-components-map"), .product(name: "W3WSwiftThemes", package: "w3w-swift-themes"), - .product(name: "W3WSwiftApi", package: "w3w-swift-wrapper") ] ), diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index 4fd6748..4a90b2a 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -14,7 +14,6 @@ import W3WSwiftThemes import W3WSwiftComponentsMap import Combine - public protocol W3WAppleMapDrawerProtocol { var mapView: MKMapView? { get } @@ -32,6 +31,7 @@ public protocol W3WAppleMapDrawerProtocol { func setRegion(_ region: MKCoordinateRegion, animated: Bool) func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) + } extension W3WAppleMapDrawerProtocol { @@ -53,11 +53,30 @@ extension W3WAppleMapDrawerProtocol { func updateGrid() { updateGridAlpha() - mapGridData?.gridUpdateDebouncer.closure = { _ in self.makeGrid() } mapGridData?.gridUpdateDebouncer.execute(()) } + func updateGridAlpha() { + + var alpha = CGFloat(0.0) + + let pointsPerSquare = self.getPointsPerSquare() + if pointsPerSquare > mapGridData?.pointsPerSquare ?? CGFloat(12.0) { + alpha = (pointsPerSquare - (mapGridData?.pointsPerSquare ?? CGFloat(12.0)) ) / CGFloat(11.001) + } + + if alpha > 1.0 { + alpha = 1.0 + + } else if alpha < 0.0 { + alpha = 0.0 + } + + mapGridData?.gridRenderer?.alpha = alpha + + } + func makeGrid() { let sw = CLLocationCoordinate2D(latitude: region.center.latitude - region.span.latitudeDelta * 3.0, longitude: region.center.longitude - region.span.longitudeDelta * 3.0) @@ -113,7 +132,7 @@ extension W3WAppleMapDrawerProtocol { if let gridLines = overlay as? W3WMapGridLines { mapGridData?.gridRenderer = W3WMapGridRenderer(multiPolyline: gridLines) mapGridData?.gridRenderer?.strokeColor = mapGridData?.mapGridColor.value.uiColor - mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.5) //mapGridData?.scheme?.styles?.lineThickness?.value ?? CGFloat(0.5) + mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.5) updateGridAlpha() return mapGridData?.gridRenderer } @@ -121,99 +140,64 @@ extension W3WAppleMapDrawerProtocol { return nil } - func getMapSquaresRenderer(overlay: MKOverlay) -> MKOverlayRenderer? { + func getMapSquaresRenderer(overlay: MKOverlay) -> MKOverlayRenderer? { + + guard let mapGridData = self.mapGridData else { return nil } - guard let mapGridData = self.mapGridData else { return nil } - if let square = overlay as? W3WMapSquareLines { let squareRenderer = W3WMapSquaresRenderer(overlay: square) - + let boxId = square.box.id //current square renderer let isSelectedSquare = mapGridData.selectedSquare?.bounds?.id == boxId - let isMarker = mapGridData.markers.contains(where: { $0.bounds?.id == square.box.id }) + let isMarker = mapGridData.markers.contains(where: { $0.bounds?.id == boxId }) - let isSquare = mapGridData.squares.contains(where: { $0.bounds?.id == square.box.id }) + let isSquare = mapGridData.squares.contains(where: { $0.bounds?.id == boxId }) - var bgSquareColor: W3WColor? = .w3wBrandBase - - if let coloredSquare = mapGridData.coloredPolylines.first(where: { $0.polyline === square }) { - bgSquareColor = coloredSquare.color - } - else { - if let color = mapGridData.overlayColors[boxId] { - bgSquareColor = color - - let coloredPolyline = ColoredPolyline(polyline: square, color: color) - mapGridData.coloredPolylines.append(coloredPolyline) - } + var bgSquareColor: W3WColor? = .w3wBrandBase + + if let color = mapGridData.overlayColors[boxId] { + bgSquareColor = color } - + let w3wImage: UIImage? - w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 55, height: 55)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 55, height: 55)) - - if (isSelectedSquare) { - if (isMarker == true) { - squareRenderer.lineWidth = 1.0 - squareRenderer.strokeColor = .black - // squareRenderer.setSquareImage(w3wImage1) - - if (isSquare == true) { // in list - squareRenderer.setSquareImage(w3wImage) - } - } - else { - squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor - squareRenderer.lineWidth = 0.1 - squareRenderer.setSquareImage(w3wImage) - } - - } else { + w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 40, height: 40)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 40, height: 40)) + + if (isSelectedSquare) { + if (isMarker == true) { + squareRenderer.lineWidth = 1.0 + squareRenderer.strokeColor = .black + + if (isSquare == true) { // in list + squareRenderer.setSquareImage(w3wImage) + } + } + else { + squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor + squareRenderer.lineWidth = 0.1 + squareRenderer.setSquareImage(w3wImage) + } + + } else { squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor squareRenderer.lineWidth = 0.1 squareRenderer.setSquareImage(w3wImage) } mapGridData.squareRenderer = squareRenderer - + return mapGridData.squareRenderer - } + } return nil } - + /// remove the grid overlay func removeGrid() { - for overlay in overlays { - if let gridOverlay = overlay as? W3WMapGridLines { - self.removeOverlay(gridOverlay) - } + let gridOverlays = overlays.compactMap { $0 as? W3WMapGridLines } + if !gridOverlays.isEmpty { + removeOverlays(gridOverlays) } - - } - - func getPointsPerMeter() -> CGFloat { - let mapCenter = mapView!.centerCoordinate - - // Create two points 1 meter apart (heading east) - let metersPerDegreeAtEquator = 111319.9 // meters per degree of longitude at the equator - let metersPerDegree = metersPerDegreeAtEquator * cos(mapCenter.latitude * .pi / 180.0) - let longitudeDelta = 1.0 / metersPerDegree - - let point1 = mapCenter - let point2 = CLLocationCoordinate2D( - latitude: mapCenter.latitude, - longitude: mapCenter.longitude + longitudeDelta - ) - - // Convert both points to screen coordinates - let point1Screen = mapView!.convert(point1, toPointTo: nil) - let point2Screen = mapView!.convert(point2, toPointTo: nil) - - // Calculate the distance in points - let distance = hypot(point2Screen.x - point1Screen.x, point2Screen.y - point1Screen.y) - - return distance } func getPointsPerSquare() -> CGFloat { @@ -221,9 +205,6 @@ extension W3WAppleMapDrawerProtocol { let threeMeterViewSquare = mapView!.convert(threeMeterMapSquare, toRectTo: nil) return threeMeterViewSquare.size.height - - // let pointsPerMeter = getPointsPerMeter() - // return pointsPerMeter * 3 // For a 3-meter square } func redrawAll() { @@ -240,7 +221,6 @@ extension W3WAppleMapDrawerProtocol { /// force a redrawing of all annotations func redrawPins() { - // print("redrawPins: \(annotations.count)") for annotation in annotations { removeAnnotation(annotation) addAnnotation(annotation) @@ -250,28 +230,8 @@ extension W3WAppleMapDrawerProtocol { func redrawSquares() { self.updateSquares() } - - func updateGridAlpha() { - - var alpha = CGFloat(0.0) - - let pointsPerSquare = self.getPointsPerSquare() - if pointsPerSquare > CGFloat(12) { - alpha = (pointsPerSquare - CGFloat(12)) / CGFloat(11.001) //001 - } - - if alpha > 1.0 { - alpha = 1.0 - - } else if alpha < 0.0 { - alpha = 0.0 - } - mapGridData?.gridRenderer?.alpha = alpha - - } - } - + extension W3WAppleMapDrawerProtocol { // MARK: Pins / Annotations @@ -296,12 +256,14 @@ extension W3WAppleMapDrawerProtocol { return nil } - - func showOutline(_ square: W3WSquare, _ annotation: W3WAppleMapAnnotation? = nil) { - W3WThread.runInBackground { + func showOutline(_ square: W3WSquare, _ annotation: W3WAppleMapAnnotation? = nil) { + + W3WThread.runInBackground { + if let s = self.ensureSquareHasCoordinates(square: square), let bounds = s.bounds, let gridData = self.mapGridData { + W3WThread.runOnMain { if (annotation?.isMarker == true ) { @@ -312,48 +274,43 @@ extension W3WAppleMapDrawerProtocol { if (annotation?.isSaved == true) { self.addUniqueSquare(s) } + } else{ self.addUniqueSquare(s) } - + + //square is selected, without background only border if let squareIsMarker = gridData.squareIsMarker { self.addUniqueSquare(squareIsMarker) } let boundsId = bounds.id gridData.overlayColors[boundsId] = annotation?.color - } - DispatchQueue.main.sync(flags: .barrier) { } - self.updateSquares() - self.updateSelectedSquare() + DispatchQueue.main.sync(flags: .barrier) { } + self.updateSquares() + self.updateSelectedSquare() } } } - - /// makes overlays from the squares - func updateSquares() { - guard let gridData = self.mapGridData else { return } - - // Create a dispatch group to coordinate synchronization + guard let gridData = self.mapGridData else { return } + let group = DispatchGroup() - - var colorsCopy = [Int64: W3WColor]() + + var colors = [Int64: W3WColor]() // Enter the group before starting work group.enter() - // Get colors from main thread W3WThread.runOnMain { - colorsCopy = gridData.overlayColors + colors = gridData.overlayColors group.leave() // Signal that colors are ready } - // Wait for colors to be copied group.wait() @@ -366,19 +323,14 @@ extension W3WAppleMapDrawerProtocol { var stateHasher = Hasher() // Hash the square IDs - for square in gridData.squares ?? [] { - + for square in gridData.squares { if let id = square.bounds?.id { - - if (square.bounds?.id == gridData.selectedSquare?.bounds?.id) { - - } stateHasher.combine(id) } } // Hash the colors - for (id, color) in colorsCopy { + for (id, color) in colors { stateHasher.combine(id) stateHasher.combine(color.description) } @@ -387,19 +339,11 @@ extension W3WAppleMapDrawerProtocol { if let selectedId = gridData.selectedSquare?.bounds?.id { stateHasher.combine(selectedId) } - - // Hash the markers commented out in your implementation - // for marker in gridData.markers { - // if let id = marker.bounds?.id { - // stateHasher.combine(id) - // } - // } - let currentStateHash = stateHasher.finalize() - + // If nothing has changed, skip the update - if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 { - return + if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 && gridData.squares.count != 0 { + return } // Update hash for next comparison @@ -407,7 +351,7 @@ extension W3WAppleMapDrawerProtocol { var boxes = [(polyline: W3WMapSquareLines, color: W3WColor?)]() - for square in gridData.squares ?? [] { + for square in gridData.squares { if let ne = square.bounds?.northEast, let sw = square.bounds?.southWest { @@ -415,32 +359,92 @@ extension W3WAppleMapDrawerProtocol { let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) - // polyline.associatedSquare = square - - let boxId = polyline.box.id ?? 0 - let color = colorsCopy[boxId] + let boxId = square.bounds?.id ?? 0//polyline.box.id ?? 0 + let color = colors[boxId] boxes.append((polyline: polyline, color: color)) } } + + + let iBoxes = boxes + W3WThread.runOnMain { - if !gridData.coloredPolylines.isEmpty { - gridData.coloredPolylines = [] - } - } - - // Create a local copy of boxes to ensure thread safety - let boxesCopy = boxes - W3WThread.runOnMain { - self.removeSquareOverlays() - // Use boxesCopy instead of the original boxes array - for box in boxesCopy { - addOverlay(box.polyline, box.color) - } + self.removeSquareOverlays() + for box in iBoxes { + addOverlay(box.polyline, box.color) } + } } + + func hideOutline(_ square: W3WSquare) { + guard let gridData = self.mapGridData else { return } + + W3WThread.runInBackground { + if let squares = self.mapGridData?.squares { + if !squares.isEmpty { + gridData.squares.removeAll(where: { s in + s.bounds?.id == square.bounds?.id + }) + } + } + W3WThread.runOnMain { + self.removeColoredSquare(square) + // self.removeSquareOverlay(square) + self.updateSquares() + } + + } + } + + public func removeColoredSquare(_ square: W3WSquare) { + + guard let gridData = self.mapGridData else { return } + + if let id = square.bounds?.id { + gridData.overlayColors.removeValue(forKey: id) + + } + } + public func removeColoredSquares() { + + guard let gridData = self.mapGridData else { return } + if !gridData.overlayColors.isEmpty { + gridData.overlayColors = [:] + } + } + + /// remove the grid overlay + func removeSquareOverlay(_ square: W3WSquare) { + // Remove them all in one batch operation + let squareOverlays = overlays.compactMap { $0 as? W3WMapSquareLines } + + if let sOverlay = squareOverlays.first(where: { $0.box.id == square.bounds?.id }) { + removeOverlay(sOverlay) + } + } + + /// remove the grid overlay + func removeSquareOverlays() { + for overlay in overlays { + if let squareOverlay = overlay as? W3WMapSquareLines { + removeOverlay(squareOverlay) + } + } + } + + func removeSelectedSquare() { + guard let gridData = self.mapGridData else { return } + + let markers = gridData.markers + if !markers.isEmpty { + gridData.markers.removeAll() + } + + } + func updateSelectedSquare() { guard let gridData = self.mapGridData else { return } @@ -450,7 +454,7 @@ extension W3WAppleMapDrawerProtocol { func makeMarkers(_ markers: [W3WSquare]?) { - var boxes1 = [MKPolyline]() + var boxes = [MKPolyline]() for (index, marker) in (markers ?? []).enumerated() { @@ -459,13 +463,12 @@ extension W3WAppleMapDrawerProtocol { let nw1 = CLLocationCoordinate2D(latitude: ne1.latitude, longitude: sw1.longitude) let se1 = CLLocationCoordinate2D(latitude: sw1.latitude, longitude: ne1.longitude) - boxes1.append(W3WMapSquareLines(coordinates: [nw1, ne1, se1, sw1, nw1], count: 5)) + boxes.append(W3WMapSquareLines(coordinates: [nw1, ne1, se1, sw1, nw1], count: 5)) } } W3WThread.runOnMain { - - for bx in boxes1 { + for bx in boxes { addOverlay(bx) } } @@ -489,15 +492,6 @@ extension W3WAppleMapDrawerProtocol { return nil } - /// remove the grid overlay - func removeSquareOverlays() { - for overlay in overlays { - if let squareOverlay = overlay as? W3WMapSquareLines { - removeOverlay(squareOverlay) - } - } - } - func hideOutline(_ words: String) { self.mapGridData?.squares.removeAll(where: { s in return s.words == words @@ -674,7 +668,7 @@ extension W3WAppleMapDrawerProtocol { // self.errorHandler(error: error) completion(result) } - + // else if the square has no words and no coordinates then it is useless and we omit it } else { completion(nil) @@ -683,7 +677,7 @@ extension W3WAppleMapDrawerProtocol { // else if the square has coordinates but no words } else if square.words == nil { if let coordinates = square.coordinates { - self.mapGridData?.w3w?.convertTo3wa(coordinates: coordinates, language: self.mapGridData?.language ?? W3WSettings.defaultLanguage ) { result, error in + self.mapGridData?.w3w?.convertTo3wa(coordinates: coordinates, language: self.mapGridData?.language ?? W3WSettings.defaultLanguage ) { result, error in // self.errorHandler(error: error) completion(result) } @@ -723,7 +717,6 @@ extension W3WAppleMapDrawerProtocol { // if there's a bad square then error out if squaresWithCoords.count != words?.count { completion(nil, W3WError.message("Invalid three word address, or coordinates passed into map function")) - // otherwise go for it } else { self.addMarker(at: squaresWithCoords, color: color, type: type) @@ -733,13 +726,12 @@ extension W3WAppleMapDrawerProtocol { } } - /// put a what3words annotation on the map showing the address, and optionally center the map around it func addMarker(at square: W3WSquare?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { - W3WThread.runOnMain { + W3WThread.runInBackground { if let sq = square { - W3WThread.runInBackground { if let s = self.completeSquareWithCoordinates(square: sq) { + W3WThread.runOnMain { self.addAnnotation(square: s, color: color, type: type) completion(s , nil) } @@ -748,7 +740,19 @@ extension W3WAppleMapDrawerProtocol { } } - ///////////// + /// add an annotation to the map given a square this compensates for missing words or missing + /// coordiantes, and does nothing if neither is present + /// this is the one that actually does the work. The other addAnnotations calls end up calling this one. + func addAnnotation(square: W3WSquare, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false) { + // W3WThread.runOnMain { + W3WThread.runInBackground { + W3WThread.runOnMain { + self.removeMarker(at: square) + addAnnotation(W3WAppleMapAnnotation(square: square, color: color, type: type, isMarker: isMarker, isMark: isMark, isSaved: isSaved)) + } + } + } + /// func addSelectedMarker(at square: W3WSquare?, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false, completion: @escaping MarkerCompletion = { _,_ in }) { W3WThread.runOnMain { @@ -762,25 +766,7 @@ extension W3WAppleMapDrawerProtocol { } } } - - - - /// add an annotation to the map given a square this compensates for missing words or missing - /// coordiantes, and does nothing if neither is present - /// this is the one that actually does the work. The other addAnnotations calls end up calling this one. - func addAnnotation(square: W3WSquare, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false) { - W3WThread.runOnMain { - W3WThread.runInBackground { - if let s = self.completeSquareWithCoordinates(square: square) { - W3WThread.runOnMain { - self.removeMarker(at: square) - addAnnotation(W3WAppleMapAnnotation(square: s, color: color, type: type, isMarker: isMarker, isMark: isMark, isSaved: isSaved)) - } - } - } - } - } - + func addCirclePin(square: W3WSquare, color: W3WColor? = nil) { W3WThread.runOnMain { // W3WThread.runInBackground { @@ -791,12 +777,10 @@ extension W3WAppleMapDrawerProtocol { func addMarkerAsCircle(at square: W3WSquare?, color: W3WColor? = nil, completion: @escaping MarkerCompletion = { _,_ in }) { W3WThread.runOnMain { - // W3WThread.runInBackground { if let s = square { self.addCirclePin(square: s, color: color) completion(s , nil) } - // } } } @@ -827,7 +811,6 @@ extension W3WAppleMapDrawerProtocol { } } - /// put a what3words annotation on the map showing the address func addMarker(at coordinates: CLLocationCoordinate2D?, color: W3WColor? = nil, type: W3WMarkerType, completion: @escaping MarkerCompletion = { _,_ in }) { W3WThread.runOnMain { @@ -890,6 +873,20 @@ extension W3WAppleMapDrawerProtocol { extension W3WAppleMapDrawerProtocol { + /// remove a what3words annotation from the map if it is present + /// this is the one that actually does the work. The other remove calls + /// end up calling this one. + + public func removeMarker(at square: W3WSquare?) { + if let s = square { + if let annotation = findAnnotation(s) { + removeAnnotation(annotation) + removeColoredSquare(s) + hideOutline(s) + } + } + } + /// remove a what3words annotation from the map if it is present public func removeMarker(at suggestion: W3WSuggestion?) { if let words = suggestion?.words { @@ -897,20 +894,20 @@ extension W3WAppleMapDrawerProtocol { } } - public func removeMarker(at words: String?) { - if let w = words { - for annotation in annotations { - if let a = annotation as? W3WAppleMapAnnotation { - if let square = a.square { - if square.words == w { - self.removeAnnotation(a) - self.hideOutline(w) - } + public func removeMarker(at words: String?) { + if let w = words { + for annotation in annotations { + if let a = annotation as? W3WAppleMapAnnotation { + if let square = a.square { + if square.words == w { + self.removeAnnotation(a) + self.hideOutline(w) } } } } } + } /// remove what3words annotations from the map if they are present public func removeMarker(at suggestions: [W3WSuggestion]?) { @@ -933,59 +930,6 @@ extension W3WAppleMapDrawerProtocol { } } - /// remove a what3words annotation from the map if it is present - /// this is the one that actually does the work. The other remove calls - /// end up calling this one. - public func removeMarker(at square: W3WSquare?) { - if let s = square { - if let annotation = findAnnotation(s) { - removeAnnotation(annotation) - hideOutline(s) - } - } - } - - func __hideOutline(_ square: W3WSquare) { - // Capture necessary data before async execution - guard let squareId = square.bounds?.id else { - print("Cannot hide square: missing bounds ID") - return - } - - W3WThread.runInBackground { - if let mapGridData = self.mapGridData, - !mapGridData.squares.isEmpty { - return - } - - // Instead of removing in-place, create a new filtered array - let filteredSquares = mapGridData?.squares.filter { square in - return square.bounds?.id != squareId - } - - // Update on main thread - W3WThread.runOnMain { - self.mapGridData?.squares = filteredSquares ?? [] - self.updateSquares() - } - } - } - - func hideOutline(_ square: W3WSquare) { - W3WThread.runInBackground { - if let s = self.mapGridData?.squares { - if s != nil { - W3WThread.runOnMain { - self.mapGridData?.squares.removeAll(where: { s in - return s.bounds?.id == square.bounds?.id - }) - } - } - } - self.updateSquares() - - } - } func hideOutlineMarker(_ square: W3WSquare) { @@ -995,11 +939,10 @@ extension W3WAppleMapDrawerProtocol { self.mapGridData?.markers.removeAll(where: { m in return m.bounds?.id == square.bounds?.id }) - }// self.mapGridData?.markers - + } + } self.updateSelectedSquare() - // self.updateSquares() } } @@ -1022,7 +965,6 @@ extension W3WAppleMapDrawerProtocol { } } } - return nil } @@ -1032,7 +974,6 @@ extension W3WAppleMapDrawerProtocol { if let w3wAnnotation = annotation as? W3WAppleMapAnnotation { removeAnnotation(w3wAnnotation) if let square = w3wAnnotation.square { - hideOutline(square) } } } @@ -1088,14 +1029,14 @@ extension W3WAppleMapDrawerProtocol { let exists = gridData.squares.contains { existingSquare in return existingSquare.bounds?.id == square.bounds?.id } - - // Only add if it doesn't exist if !exists { gridData.squares.append(square) } } } + + func removeAllSquares() { if var gridData = self.mapGridData { gridData.squares.removeAll() diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 53c679f..567b531 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -58,9 +58,6 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public private(set) var markers: [W3WSquare] = [] - // typealias MarkerCompletion = (W3WSquare?, W3WError?) -> () - - public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { self.mapView = mapView self.w3w = w3w @@ -115,6 +112,10 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } } + private func calculateZoomLevel(_ span: MKCoordinateSpan) -> Double { + return log2(360.0 / span.latitudeDelta) + } + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { updateMap() } @@ -126,6 +127,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap /// hijack this delegate call and update the grid, then pass control to the external delegate public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + updateMap() } @@ -134,28 +136,27 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { - if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { return a } return nil } - - + public func addAnnotation(_ annotation: MKAnnotation) { mapView?.addAnnotation(annotation) } public func removeAnnotation(_ annotation: MKAnnotation) { mapView?.removeAnnotation(annotation) + } public func removeOverlay(_ overlay: MKOverlay) { mapView?.removeOverlay(overlay) } - public func removeOverlays(_ overlay: [MKOverlay]) { + public func removeOverlays(_ overlays: [MKOverlay]) { mapView?.removeOverlays(overlays) } @@ -163,8 +164,8 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap mapView?.addOverlay(overlay) } - public func addOverlays(_ overlays: MKOverlay) { - mapView?.addOverlay(overlays) + public func addOverlays(_ overlays: [MKOverlay]) { + mapView?.addOverlays(overlays) } public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { @@ -172,21 +173,16 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { - + if let color = color, let square = overlay as? W3WMapSquareLines { - - let coloredPolyline = ColoredPolyline(polyline: square, color: color) - mapGridData?.coloredPolylines.append(coloredPolyline) + mapGridData?.overlayColors[square.box.id] = color } - mapView?.addOverlay(overlay) - } - + +} public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { if let markerView = view.annotation as? W3WAppleMapAnnotation { - if let square = markerView.square { - } } } @@ -251,12 +247,11 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } - public extension W3WAppleMapHelper { func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { - self.convertTo3wa(coordinates: coordinates ?? CLLocationCoordinate2D(), language: self.language) { [weak self] square, error in + self.convertTo3wa(coordinates: coordinates, language: self.language) { [weak self] square, error in guard let self = self else { return } @@ -268,7 +263,7 @@ public extension W3WAppleMapHelper { } if let s = square { W3WThread.runOnMain { - //self.select(at: s) + // self.select(at: s) completion(.success(s)) } } else { @@ -281,12 +276,12 @@ public extension W3WAppleMapHelper { } func select(at: W3WSquare) { - createMarkerForConditions(at: at) + createMarkerForConditions(at) } - func createMarkerForConditions(at: W3WSquare) { + func createMarkerForConditions(_ at: W3WSquare) { - var squares = self.mapGridData?.squares + let squares = self.mapGridData?.squares let selectedSquare = self.mapGridData?.selectedSquare @@ -309,11 +304,17 @@ public extension W3WAppleMapHelper { removeSelectedSquare(at: selectedSquare) addMarkerAsCircle(at: selectedSquare, color: previousColor) } + else{ + if markers != nil { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: annotation?.color) + } + } } else{ removeSelectedSquare(at: selectedSquare) } - let previousBoxId = selectedSquare.bounds?.id + addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) self.mapGridData?.selectedSquare = at @@ -325,6 +326,7 @@ public extension W3WAppleMapHelper { //squares if isMarkerinList == true { removeSelectedSquare(at: selectedSquare) + let currentBoxId = at.bounds?.id let previousBoxId = selectedSquare?.bounds?.id if isPrevMarkerinList == true { @@ -342,7 +344,6 @@ public extension W3WAppleMapHelper { if isPrevMarkerinList == true { if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { addMarkerAsCircle(at: selectedSquare, color: previousColor) - // addSelectedMarker(at: selectedSquare, color: previousColor, type: .circle, isMarker: true, isMark: true) } } addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) @@ -394,7 +395,7 @@ public extension W3WAppleMapHelper { } func removeMarker(at squares: [W3WSquare]?) { - + } func removeMarker(at suggestions: [W3WSuggestion]?) { @@ -406,7 +407,7 @@ public extension W3WAppleMapHelper { } func removeMarker(at square: W3WSquare?) { - + // removeMarker(at: square) } func removeMarker(group: String) { @@ -429,24 +430,22 @@ public extension W3WAppleMapHelper { } - public func getAllMarkers() -> [W3WSquare] { + func getAllMarkers() -> [W3WSquare] { return [W3WSquare]() } - public func removeAllMarkers() { + func removeAllMarkers() { self.markers.removeAll() - if var gridData = self.mapGridData { + if let gridData = self.mapGridData { gridData.squares.removeAll() gridData.markers.removeAll() gridData.selectedSquare = nil gridData.squareIsMarker = nil gridData.currentSquare = nil } - redrawPins() - redrawSquares() } - public func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { + func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { return nil } } @@ -474,27 +473,115 @@ extension W3WAppleMapHelper { public func updateSquare(square: W3WSquare?) { if let square = square { - // addMarker(at: square, color: nil, type: .circle) + self.mapGridData?.currentSquare = square self.select(at: square) } } - - public func updateMarkers(markers: W3WMarkersLists) { - removeAllMarkers() - let _list = markers.getLists() - for (_, list) in markers.getLists() { - for marker in list.markers { - - addMarker(at: marker, color: list.color, type: .circle) + private func getNewMarkers(markersLists: W3WMarkersLists) -> W3WMarkersLists { + guard let gridData = self.mapGridData else { + return W3WMarkersLists() } - } + + // Create a new markers list to return + let newMarkersLists = W3WMarkersLists() + + // Clear the automatically created default list + newMarkersLists.lists.removeAll() + + // Process each list in the input + for (listName, list) in markersLists.getLists() { + // Skip empty lists or default list with no color + if list.markers.isEmpty || (listName == "default" && list.color == nil) { + continue + } + + // Create a new list for this color + let newList = W3WMarkerList() + newList.color = list.color + newList.type = list.type + + // Track already processed squares for this specific list + var listProcessedIds = Set() + + for marker in list.markers { + if let bounds = marker.bounds { + let squareId = bounds.id + + // Skip if we've already processed this ID in this list + if listProcessedIds.contains(squareId) { + continue + } + + // Check if this square exists in overlay colors + if let existingColor = gridData.overlayColors[squareId] { + // Only include if the color is different + if let listColor = list.color, !colorComponentsMatch(existingColor.cgColor, listColor.cgColor) { + newList.markers.append(marker) + } + } else { + // Square doesn't exist in overlay colors, so include it + newList.markers.append(marker) + } + + // Mark this ID as processed for this list + listProcessedIds.insert(squareId) + } else { + // No bounds, include it + newList.markers.append(marker) + } + } + + // Only add lists with markers + if !newList.markers.isEmpty { + newMarkersLists.add(listName: listName, list: newList) + } + } + + return newMarkersLists + } + // Helper function to compare color components + private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { + // Check if color spaces match + guard color1.colorSpace?.model == color2.colorSpace?.model else { + return false + } + + // Get components + let components1 = color1.components ?? [] + let components2 = color2.components ?? [] + + // Check if component counts match + guard components1.count == components2.count else { + return false + } + + // Compare components with a small tolerance + let tolerance: CGFloat = 0.001 + for i in 0.. tolerance { + return false + } + } + + return true } + public func updateMarkers(markersLists: W3WMarkersLists) { + if !markersLists.getLists().isEmpty { + for (_, list) in markersLists.getLists() { + for marker in list.markers { + addMarker(at: marker, color: list.color, type: list.type ?? .circle) + } + } + } + } + + public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in - guard let self = self else { return } + guard self != nil else { return } if let error = error { W3WThread.runOnMain { @@ -507,14 +594,6 @@ extension W3WAppleMapHelper { } } } - - func findSquare(_ square: W3WSquare) -> W3WSquare? { - return (self.mapGridData?.squares ?? []).first { s in - let idMatch = s.bounds?.id == square.bounds?.id - return idMatch - } - return nil - } } extension W3WAppleMapHelper { @@ -526,9 +605,5 @@ extension W3WAppleMapHelper { public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { mapView?.setCenter(coordinate, animated: animated) } - - public func updateZoomLevel() { - - } - + } diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift index d5d3a0b..fe77de5 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -37,7 +37,7 @@ public class W3WAppleMapGridData { let squarePinFrameSize = CGFloat(50.0) public var onError: W3WMapErrorHandler = { _ in } - + var gridRendererPointer: W3WMapGridRenderer? = nil var squareRendererPointer: W3WMapSquaresRenderer? = nil var gridLinePointer: W3WMapGridLines? = nil @@ -60,20 +60,12 @@ public class W3WAppleMapGridData { var currentSquare: W3WSquare? = nil - var currentOverlays: [Int64: MKOverlay] = [:] + var isUpdatingSquares = false var overlayColors: [Int64: W3WColor] = [:] - var previousSquareIds = Set() - - var previousSquareIdsHash: Int? - - var previousStateHash: Int? - - var coloredPolylines = [ColoredPolyline]() - - ///keep track of annoptation positions globally - var annotationPositions: [String: CGPoint] = [:] + var previousStateHash: Int? = 0 + var scheme: W3WScheme? = .w3w @@ -155,42 +147,27 @@ public class W3WMapGridLines: MKMultiPolyline { public class W3WMapGridRenderer: MKMultiPolylineRenderer { } -public class ColoredPolyline { - - let polyline: W3WMapSquareLines - let color: W3WColor? - - init(polyline: W3WMapSquareLines, color: W3WColor?) { - self.polyline = polyline - self.color = color - } - -} - public class W3WMapSquareLines: MKPolyline { - -// var associatedSquare: W3WSquare? - + var box: W3WBaseBox { let points = self.points() let sw = points[3].coordinate // SW is at index 3 let ne = points[1].coordinate // NE is at index 1 return W3WBaseBox(southWest: sw, northEast: ne) } - + convenience init? (bounds: W3WBaseBox?) { - guard let ne = bounds?.northEast, - let sw = bounds?.southWest else { - return nil + guard let ne = bounds?.northEast, + let sw = bounds?.southWest else { + return nil + } + + let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) + let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) + let coordinates = [nw, ne, se, sw, nw] + + self.init(coordinates: coordinates, count: 5) } - - let nw = CLLocationCoordinate2D(latitude: ne.latitude, longitude: sw.longitude) - let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) - let coordinates = [nw, ne, se, sw, nw] - - self.init(coordinates: coordinates, count: 5) - // self.associatedSquare = square - } } public class W3WMapSquaresRenderer: MKPolylineRenderer { diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WColor+.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WColor+.swift new file mode 100644 index 0000000..6008f25 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WColor+.swift @@ -0,0 +1,21 @@ +// +// W3WColor+.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 25/4/25. +// +import W3WSwiftThemes +import W3WSwiftCore + +extension W3WColor: Equatable { + + public static func == (lhs: W3WColor, rhs: W3WColor) -> Bool { + + guard lhs.colors.count == rhs.colors.count else { return false } + + for(mode, color) in lhs.colors { + guard let rhsColor = rhs.colors[mode], rhsColor == color else { return false } + } + return true + } +} diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index 34951f7..c48f2e8 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -18,9 +18,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView public var transitionScale = W3WMapScale(pointsPerMeter: CGFloat(4.0)) - - // public var transitionScaleConversion = transitionScale.value - + public var subscriptions = W3WEventsSubscriptions() /// The map view model to use @@ -40,10 +38,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView return 20 - zoomExponent } - // var output: W3WEvent - - // var mapView: MKMapView? - /// The available map types public var types: [W3WMapType] { get { return [.standard, .satellite, .hybrid] } } @@ -69,17 +63,10 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView bind() - tesFuncs() - attachTapRecognizer() } - func tesFuncs() { - - // addTestMarkers() - - } - + /// Make an Apple Map Kit Map required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -111,17 +98,13 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView public func getCameraState() -> W3WMapCamera { let mapView = w3wHelper.mapView - - return W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size )) + return + W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size )) + } public func set(scheme: W3WScheme?) { - w3wHelper.set(scheme: scheme) - - // w3wHelper.gridData?.gridRenderer?.lineWidth = scheme?.styles?.lineThickness?.value ?? CGFloat(0.5) - // w3wHelper.gridData!.gridRenderer?.strokeColor = scheme?.colors?.line?.current.uiColor - } public func updateSavedLists(markers: W3WMarkersLists) { @@ -132,7 +115,10 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView subscribe(to: self.viewModel.input.markers) { [weak self] markers in guard let self = self else { return } - w3wHelper.updateMarkers(markers: markers) + + if !markers.getLists().isEmpty { + w3wHelper.updateMarkers(markersLists: markers) + } } subscribe(to: self.viewModel.input.camera) { [weak self] camera in @@ -152,29 +138,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView } - func addTestMarkers(){ - - w3wHelper.addMarker(at: "become.outlooks.rising", color: .white, type: .circle)// - - let coordinate = CLLocationCoordinate2D( - latitude: 10.780468, - longitude: 106.705438 - ) - - let span = MKCoordinateSpan( - latitudeDelta: 0.0002, - longitudeDelta: 0.0002 - ) - - let region = MKCoordinateRegion( - center: coordinate, - span: span - ) - // self.setCenter(coordinate, animated: true) - // self.setRegion(region, animated: true) - w3wHelper.setRegion(region, animated: true) - } - func attachTapRecognizer() { let mapView = w3wHelper.mapView @@ -223,10 +186,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView guard let self = self else { return } switch result { case .success(let square): - //build the list - // let markersList = W3WMarkersLists(defaultColor: .w3wBrandBase) - // markersList.add(listName: "favorites", color: .w3wBrandBase) - // markersList.add(square: square, listName: "favorites") self.viewModel.output.send(.selected(square)) case .failure(let error): print("Show Error") From accbd80cb363102cf11f43baed42557472684314 Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Tue, 6 May 2025 20:34:51 +0700 Subject: [PATCH 07/20] add new seting and fix grid line thickness and also the square line thickness and color --- .../Drawing/W3WAppleMapDrawer.swift | 31 +++++++------- .../Helper/W3WAppleMapHelper.swift | 42 ++++++++++++++----- .../Type/W3WAppleMapGridData.swift | 15 +++++-- .../Type/W3WSettings.swift | 13 ++++++ .../View/W3WAppleMapView.swift | 13 ++---- 5 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 Sources/W3WSwiftComponentsMapApple/Type/W3WSettings.swift diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index 4a90b2a..e59b158 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -55,6 +55,7 @@ extension W3WAppleMapDrawerProtocol { updateGridAlpha() mapGridData?.gridUpdateDebouncer.closure = { _ in self.makeGrid() } mapGridData?.gridUpdateDebouncer.execute(()) + } func updateGridAlpha() { @@ -74,7 +75,13 @@ extension W3WAppleMapDrawerProtocol { } mapGridData?.gridRenderer?.alpha = alpha + + } + + func changeLineThicknessIfNeeded(value: CGFloat) { + let gridLineThickness: W3WLineThickness = 4.0 + self.mapGridData?.mapGridLineThickness.send(gridLineThickness) } func makeGrid() { @@ -84,7 +91,7 @@ extension W3WAppleMapDrawerProtocol { // call w3w api for lines, if the area is not too great if let distance = mapGridData?.w3w?.distance(from: sw, to: ne) { - if distance < W3WSettings.maxMetersDiagonalForGrid { + if distance < W3WSettings.maxMetersDiagonalForGrid && distance > 0.0 { mapGridData?.w3w?.gridSection(southWest:sw, northEast:ne) { lines, error in self.makeNewGrid(lines: lines) @@ -131,8 +138,8 @@ extension W3WAppleMapDrawerProtocol { if let gridLines = overlay as? W3WMapGridLines { mapGridData?.gridRenderer = W3WMapGridRenderer(multiPolyline: gridLines) - mapGridData?.gridRenderer?.strokeColor = mapGridData?.mapGridColor.value.uiColor - mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.5) + mapGridData?.gridRenderer?.strokeColor = mapGridData?.scheme?.colors?.line?.uiColor + mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.7) updateGridAlpha() return mapGridData?.gridRenderer } @@ -147,7 +154,8 @@ extension W3WAppleMapDrawerProtocol { if let square = overlay as? W3WMapSquareLines { let squareRenderer = W3WMapSquaresRenderer(overlay: square) - let boxId = square.box.id //current square renderer + let boxId = square.box.id + let isSelectedSquare = mapGridData.selectedSquare?.bounds?.id == boxId let isMarker = mapGridData.markers.contains(where: { $0.bounds?.id == boxId }) @@ -163,29 +171,26 @@ extension W3WAppleMapDrawerProtocol { let w3wImage: UIImage? w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 40, height: 40)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 40, height: 40)) + squareRenderer.strokeColor = mapGridData.scheme?.colors?.line?.uiColor + squareRenderer.lineWidth = mapGridData.mapSquareLineThickness.value.value + if (isSelectedSquare) { if (isMarker == true) { squareRenderer.lineWidth = 1.0 - squareRenderer.strokeColor = .black if (isSquare == true) { // in list squareRenderer.setSquareImage(w3wImage) } } else { - squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor - squareRenderer.lineWidth = 0.1 squareRenderer.setSquareImage(w3wImage) } } else { - squareRenderer.strokeColor = W3WColor.mediumGrey.uiColor - squareRenderer.lineWidth = 0.1 squareRenderer.setSquareImage(w3wImage) } mapGridData.squareRenderer = squareRenderer - return mapGridData.squareRenderer } @@ -359,7 +364,7 @@ extension W3WAppleMapDrawerProtocol { let se = CLLocationCoordinate2D(latitude: sw.latitude, longitude: ne.longitude) let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) - let boxId = square.bounds?.id ?? 0//polyline.box.id ?? 0 + let boxId = square.bounds?.id ?? 0 let color = colors[boxId] boxes.append((polyline: polyline, color: color)) @@ -391,7 +396,6 @@ extension W3WAppleMapDrawerProtocol { } W3WThread.runOnMain { self.removeColoredSquare(square) - // self.removeSquareOverlay(square) self.updateSquares() } @@ -401,7 +405,6 @@ extension W3WAppleMapDrawerProtocol { public func removeColoredSquare(_ square: W3WSquare) { guard let gridData = self.mapGridData else { return } - if let id = square.bounds?.id { gridData.overlayColors.removeValue(forKey: id) @@ -744,7 +747,6 @@ extension W3WAppleMapDrawerProtocol { /// coordiantes, and does nothing if neither is present /// this is the one that actually does the work. The other addAnnotations calls end up calling this one. func addAnnotation(square: W3WSquare, color: W3WColor? = nil, type: W3WMarkerType, isMarker: Bool? = false, isMark: Bool? = false, isSaved: Bool? = false) { - // W3WThread.runOnMain { W3WThread.runInBackground { W3WThread.runOnMain { self.removeMarker(at: square) @@ -870,7 +872,6 @@ extension W3WAppleMapDrawerProtocol { } } - extension W3WAppleMapDrawerProtocol { /// remove a what3words annotation from the map if it is present diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 567b531..0d39026 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -18,7 +18,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public var mapGridData: W3WAppleMapGridData? - public var scheme: W3WScheme? = .w3w + public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() public var region: MKCoordinateRegion { return mapView?.region ?? MKCoordinateRegion() @@ -74,9 +74,16 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } } - func setGridLine() { + + func setGridLineThickness(value: W3WLineThickness) { + if let gridData = mapGridData { + gridData.mapGridLineThickness.send(value) + } + } + + func setSquareLineThickness(value: W3WLineThickness) { if let gridData = mapGridData { - gridData.mapGridLineThickness.send(4.0) + gridData.mapSquareLineThickness.send(value) } } @@ -112,12 +119,9 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap } } - private func calculateZoomLevel(_ span: MKCoordinateSpan) -> Double { - return log2(360.0 / span.latitudeDelta) - } - public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { updateMap() + // changeLineThicknessIfNeeded() } /// hijack this delegate call and update the grid, then pass control to the external delegate @@ -127,7 +131,6 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap /// hijack this delegate call and update the grid, then pass control to the external delegate public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { - updateMap() } @@ -315,7 +318,7 @@ public extension W3WAppleMapHelper { removeSelectedSquare(at: selectedSquare) } - addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) + addSelectedMarker(at: at, color: scheme?.colors?.border, type: .square, isMarker: true, isMark: true) self.mapGridData?.selectedSquare = at return @@ -346,7 +349,7 @@ public extension W3WAppleMapHelper { addMarkerAsCircle(at: selectedSquare, color: previousColor) } } - addSelectedMarker(at: at, color: .darkBlue, type: .square, isMarker: true, isMark: true) + addSelectedMarker(at: at, color: scheme?.colors?.border, type: .square, isMarker: true, isMark: true) } self.mapGridData?.selectedSquare = at @@ -568,7 +571,8 @@ extension W3WAppleMapHelper { } public func updateMarkers(markersLists: W3WMarkersLists) { - if !markersLists.getLists().isEmpty { + let newMarkers = self.getNewMarkers(markersLists: markersLists) + if !newMarkers.getLists().isEmpty { for (_, list) in markersLists.getLists() { for marker in list.markers { addMarker(at: marker, color: list.color, type: list.type ?? .circle) @@ -607,3 +611,19 @@ extension W3WAppleMapHelper { } } + +extension W3WAppleMapHelper { + + func changeLineThicknessIfNeeded() { + + let scale = W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size) + + let gridLineThickness = scale.gridLineThickness() + let squareLineThickness = scale.squareLineThickness() + + self.setGridLineThickness(value: gridLineThickness) + self.setSquareLineThickness(value: squareLineThickness) + + } + +} diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift index fe77de5..14dfbed 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -22,13 +22,13 @@ public class W3WAppleMapGridData { let squareColor = W3WLive(.w3wBrandBase) let mapGridColor = W3WLive(.mediumGrey) - let mapGridLineThickness = W3WLive(0.5) + let mapGridLineThickness = W3WLive(0.7) let mapSquareColor = W3WLive(.black) let mapSquareLineThickness = W3WLive(0.1) - let selectedSquareBorderColor = W3WLive(.black) - let selectedSquareThickness = W3WLive(0.5) +// let selectedSquareBorderColor = W3WLive(.black) +// let selectedSquareThickness = W3WLive(0.5) let pinWidth = CGFloat(30.0) let pinHeight = CGFloat(30.0) @@ -105,6 +105,7 @@ public class W3WAppleMapGridData { self.mapGridColorListener() self.squareColorListener() self.squareLineThicknessListener() + self.gridLineThicknessListener() } private func mapGridColorListener() { @@ -131,6 +132,14 @@ public class W3WAppleMapGridData { .store(in: &cancellables) } + private func gridLineThicknessListener() { + mapGridLineThickness + .sink { [weak self] lineThickness in + self?.gridRenderer?.lineWidth = lineThickness.value + } + .store(in: &cancellables) + } + public func set(scheme: W3WScheme?) { self.scheme = scheme } diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WSettings.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WSettings.swift new file mode 100644 index 0000000..4302925 --- /dev/null +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WSettings.swift @@ -0,0 +1,13 @@ +// +// W3WSettings.swift +// w3w-swift-components-map-apple +// +// Created by Henry Ng on 6/5/25. +// + +import W3WSwiftCore + +extension W3WSettings { + public static var maxMetersDiagonalForGrid = 4000.0 + +} diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index c48f2e8..0635afb 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -9,7 +9,7 @@ import MapKit import W3WSwiftCore import W3WSwiftComponentsMap import W3WSwiftCore -import W3WSwiftApi +//import W3WSwiftApi import W3WSwiftDesign @@ -32,12 +32,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView private var onError: W3WMapErrorHandler = { _ in } - var zoomLevel: Double { - let zoomScale = self.visibleMapRect.size.width / Double(self.frame.size.width) - let zoomExponent = log2(zoomScale) - return 20 - zoomExponent - } - /// The available map types public var types: [W3WMapType] { get { return [.standard, .satellite, .hybrid] } } @@ -99,7 +93,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView public func getCameraState() -> W3WMapCamera { let mapView = w3wHelper.mapView return - W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size )) + W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size)) } @@ -260,5 +254,6 @@ extension W3WAppleMapView: MKMapViewDelegate { w3wHelper.mapView(mapView, didSelect: view) } - + } + From db3a57daaf69810a6d797d022bf3db61cf0325cf Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Wed, 7 May 2025 16:11:50 +0700 Subject: [PATCH 08/20] remove all markers before adding the new ones --- .../Drawing/W3WAppleMapDrawer.swift | 25 ------------------- .../Helper/W3WAppleMapHelper.swift | 15 +++++++---- .../Type/W3WAppleMapGridData.swift | 1 - .../View/W3WAppleMapView.swift | 8 ++---- 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index e59b158..d1fa134 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -46,7 +46,6 @@ extension W3WAppleMapDrawerProtocol { redrawPins() } - mapGridData?.lastZoomPointsPerSquare = squareSize } } @@ -968,18 +967,6 @@ extension W3WAppleMapDrawerProtocol { } return nil } - - /// remove what3words annotations from the map if they are present - public func removeAllMarkers() { - for annotation in annotations { - if let w3wAnnotation = annotation as? W3WAppleMapAnnotation { - removeAnnotation(w3wAnnotation) - if let square = w3wAnnotation.square { - } - } - } - } - public func getAllMarkers() -> [W3WSquare] { var squares = [W3WSquare]() @@ -1035,18 +1022,6 @@ extension W3WAppleMapDrawerProtocol { } } } - - - - func removeAllSquares() { - if var gridData = self.mapGridData { - gridData.squares.removeAll() - gridData.markers.removeAll() - gridData.selectedSquare = nil - gridData.squareIsMarker = nil - gridData.currentSquare = nil - } - } } diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 0d39026..e1bd32a 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -18,7 +18,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public var mapGridData: W3WAppleMapGridData? - public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() + public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() public var region: MKCoordinateRegion { return mapView?.region ?? MKCoordinateRegion() @@ -446,6 +446,11 @@ public extension W3WAppleMapHelper { gridData.selectedSquare = nil gridData.squareIsMarker = nil gridData.currentSquare = nil + gridData.overlayColors = [:] + + for annotation in annotations { + removeAnnotation(annotation) + } } } func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { @@ -571,8 +576,9 @@ extension W3WAppleMapHelper { } public func updateMarkers(markersLists: W3WMarkersLists) { - let newMarkers = self.getNewMarkers(markersLists: markersLists) - if !newMarkers.getLists().isEmpty { + // let newMarkers = self.getNewMarkers(markersLists: markersLists) + self.removeAllMarkers() + if !markersLists.getLists().isEmpty { for (_, list) in markersLists.getLists() { for marker in list.markers { addMarker(at: marker, color: list.color, type: list.type ?? .circle) @@ -583,7 +589,7 @@ extension W3WAppleMapHelper { public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { - + self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in guard self != nil else { return } @@ -609,7 +615,6 @@ extension W3WAppleMapHelper { public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { mapView?.setCenter(coordinate, animated: animated) } - } extension W3WAppleMapHelper { diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift index 14dfbed..497c648 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -66,7 +66,6 @@ public class W3WAppleMapGridData { var previousStateHash: Int? = 0 - var scheme: W3WScheme? = .w3w var mapZoomLevel = CGFloat(0.0) diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index 0635afb..ff5380b 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -9,14 +9,11 @@ import MapKit import W3WSwiftCore import W3WSwiftComponentsMap import W3WSwiftCore -//import W3WSwiftApi import W3WSwiftDesign - /// An Apple Map Kit Map public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapViewProtocol, W3WEventSubscriberProtocol { - public var transitionScale = W3WMapScale(pointsPerMeter: CGFloat(4.0)) public var subscriptions = W3WEventsSubscriptions() @@ -59,7 +56,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView attachTapRecognizer() } - + /// Make an Apple Map Kit Map required init?(coder: NSCoder) { @@ -113,7 +110,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView if !markers.getLists().isEmpty { w3wHelper.updateMarkers(markersLists: markers) } - } + } subscribe(to: self.viewModel.input.camera) { [weak self] camera in guard let self = self else { return } @@ -251,7 +248,6 @@ extension W3WAppleMapView: MKMapViewDelegate { if let markerView = view.annotation as? W3WAppleMapAnnotation { } - w3wHelper.mapView(mapView, didSelect: view) } From a9a131c68d887e4285fbf68b7049bf9b252cfbda Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Wed, 7 May 2025 16:19:15 +0700 Subject: [PATCH 09/20] remove changethicknessifneeded() from Drawer --- .../Drawing/W3WAppleMapDrawer.swift | 9 +-------- .../Helper/W3WAppleMapHelper.swift | 6 ++++-- .../View/W3WAppleMapView.swift | 1 - 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index d1fa134..6792387 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -74,15 +74,8 @@ extension W3WAppleMapDrawerProtocol { } mapGridData?.gridRenderer?.alpha = alpha - - } - - func changeLineThicknessIfNeeded(value: CGFloat) { - - let gridLineThickness: W3WLineThickness = 4.0 - self.mapGridData?.mapGridLineThickness.send(gridLineThickness) } - + func makeGrid() { let sw = CLLocationCoordinate2D(latitude: region.center.latitude - region.span.latitudeDelta * 3.0, longitude: region.center.longitude - region.span.longitudeDelta * 3.0) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index e1bd32a..c0e36ab 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -121,7 +121,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { updateMap() - // changeLineThicknessIfNeeded() + changeLineThicknessIfNeeded() } /// hijack this delegate call and update the grid, then pass control to the external delegate @@ -628,7 +628,9 @@ extension W3WAppleMapHelper { self.setGridLineThickness(value: gridLineThickness) self.setSquareLineThickness(value: squareLineThickness) - + + print("gridLineThickness: \(gridLineThickness)") + print("squareLineThickness: \(squareLineThickness)") } } diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index ff5380b..8959042 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -252,4 +252,3 @@ extension W3WAppleMapView: MKMapViewDelegate { } } - From 5e90459d77c67521000876237c4ce7fd2065475d Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Thu, 8 May 2025 00:46:46 +0700 Subject: [PATCH 10/20] fix remove old markers when receiving a new list --- .../Drawing/W3WAppleMapDrawer.swift | 20 ++++---- .../Helper/W3WAppleMapHelper.swift | 48 ++++++++----------- .../Type/W3WAppleMapGridData.swift | 9 +--- .../Type/W3WImageCache.swift | 2 +- .../View/W3WAppleMapView.swift | 14 ++---- 5 files changed, 39 insertions(+), 54 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index 6792387..8601fb1 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -154,7 +154,7 @@ extension W3WAppleMapDrawerProtocol { let isSquare = mapGridData.squares.contains(where: { $0.bounds?.id == boxId }) - var bgSquareColor: W3WColor? = .w3wBrandBase + var bgSquareColor: W3WColor? if let color = mapGridData.overlayColors[boxId] { bgSquareColor = color @@ -162,7 +162,7 @@ extension W3WAppleMapDrawerProtocol { let w3wImage: UIImage? w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 40, height: 40)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 40, height: 40)) - + squareRenderer.strokeColor = mapGridData.scheme?.colors?.line?.uiColor squareRenderer.lineWidth = mapGridData.mapSquareLineThickness.value.value @@ -179,7 +179,10 @@ extension W3WAppleMapDrawerProtocol { } } else { - squareRenderer.setSquareImage(w3wImage) + + if (bgSquareColor != nil) { + squareRenderer.setSquareImage(w3wImage) + } } mapGridData.squareRenderer = squareRenderer @@ -299,13 +302,13 @@ extension W3WAppleMapDrawerProtocol { let group = DispatchGroup() - var colors = [Int64: W3WColor]() + var squareId_color = [Int64: W3WColor]() // Enter the group before starting work group.enter() // Get colors from main thread W3WThread.runOnMain { - colors = gridData.overlayColors + squareId_color = gridData.overlayColors group.leave() // Signal that colors are ready } // Wait for colors to be copied @@ -327,7 +330,7 @@ extension W3WAppleMapDrawerProtocol { } // Hash the colors - for (id, color) in colors { + for (id, color) in squareId_color { stateHasher.combine(id) stateHasher.combine(color.description) } @@ -336,6 +339,7 @@ extension W3WAppleMapDrawerProtocol { if let selectedId = gridData.selectedSquare?.bounds?.id { stateHasher.combine(selectedId) } + let currentStateHash = stateHasher.finalize() // If nothing has changed, skip the update @@ -357,7 +361,7 @@ extension W3WAppleMapDrawerProtocol { let polyline = W3WMapSquareLines(coordinates: [nw, ne, se, sw, nw], count: 5) let boxId = square.bounds?.id ?? 0 - let color = colors[boxId] + let color = squareId_color[boxId] boxes.append((polyline: polyline, color: color)) } @@ -531,7 +535,7 @@ extension W3WAppleMapDrawerProtocol { let pinImageWidth = squareSize let pinImageHeight = squareSize - pinImage = W3WImage(drawing: .mapPin, colors: .standardMaps.with(background: color).with(foreground: color?.complimentaryTextColor())) + pinImage = W3WImage(drawing: .mapPin, colors: .standard.with(background: color).with(foreground: color?.complimentaryTextColor())) .get(size: W3WIconSize(value: CGSize(width: pinImageWidth , height: pinImageHeight))) centerOffset = CGPoint(x: 0.0, y: (-20.0)) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index c0e36ab..451d9a4 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -112,8 +112,8 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public func getType() -> W3WMapType { switch self.mapType { case .standard: return "standard" - case .satellite: return "hybridFlyover" - case .hybrid: return "hybrid" + case .satellite: return "satellite" + case .hybrid: return "hybridFlyover" default: return "standard" } @@ -121,7 +121,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { updateMap() - changeLineThicknessIfNeeded() + // changeLineThicknessIfNeeded() } /// hijack this delegate call and update the grid, then pass control to the external delegate @@ -317,8 +317,8 @@ public extension W3WAppleMapHelper { else{ removeSelectedSquare(at: selectedSquare) } - - addSelectedMarker(at: at, color: scheme?.colors?.border, type: .square, isMarker: true, isMark: true) + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: nil, type: .square, isMarker: true, isMark: true) self.mapGridData?.selectedSquare = at return @@ -349,7 +349,10 @@ public extension W3WAppleMapHelper { addMarkerAsCircle(at: selectedSquare, color: previousColor) } } - addSelectedMarker(at: at, color: scheme?.colors?.border, type: .square, isMarker: true, isMark: true) + + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + + addSelectedMarker(at: at, color: nil, type: .square, isMarker: true, isMark: true) } self.mapGridData?.selectedSquare = at @@ -443,16 +446,24 @@ public extension W3WAppleMapHelper { if let gridData = self.mapGridData { gridData.squares.removeAll() gridData.markers.removeAll() - gridData.selectedSquare = nil - gridData.squareIsMarker = nil - gridData.currentSquare = nil + // gridData.selectedSquare = nil + // gridData.squareIsMarker = nil + // gridData.currentSquare = nil gridData.overlayColors = [:] + gridData.previousStateHash = nil for annotation in annotations { removeAnnotation(annotation) } } } + + func redraw(){ + redrawGrid() + redrawSquares() + redrawPins() + } + func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { return nil } @@ -548,6 +559,7 @@ extension W3WAppleMapHelper { return newMarkersLists } + // Helper function to compare color components private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { // Check if color spaces match @@ -616,21 +628,3 @@ extension W3WAppleMapHelper { mapView?.setCenter(coordinate, animated: animated) } } - -extension W3WAppleMapHelper { - - func changeLineThicknessIfNeeded() { - - let scale = W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size) - - let gridLineThickness = scale.gridLineThickness() - let squareLineThickness = scale.squareLineThickness() - - self.setGridLineThickness(value: gridLineThickness) - self.setSquareLineThickness(value: squareLineThickness) - - print("gridLineThickness: \(gridLineThickness)") - print("squareLineThickness: \(squareLineThickness)") - } - -} diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift index 497c648..229dcc5 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WAppleMapGridData.swift @@ -22,14 +22,11 @@ public class W3WAppleMapGridData { let squareColor = W3WLive(.w3wBrandBase) let mapGridColor = W3WLive(.mediumGrey) - let mapGridLineThickness = W3WLive(0.7) + let mapGridLineThickness = W3WLive(0.5) - let mapSquareColor = W3WLive(.black) +// let mapSquareColor = W3WLive(.black) let mapSquareLineThickness = W3WLive(0.1) -// let selectedSquareBorderColor = W3WLive(.black) -// let selectedSquareThickness = W3WLive(0.5) - let pinWidth = CGFloat(30.0) let pinHeight = CGFloat(30.0) let pinFrameSize = CGFloat(30.0) @@ -60,8 +57,6 @@ public class W3WAppleMapGridData { var currentSquare: W3WSquare? = nil - var isUpdatingSquares = false - var overlayColors: [Int64: W3WColor] = [:] var previousStateHash: Int? = 0 diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift index bbcd210..b5aacf2 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift @@ -21,7 +21,7 @@ class W3WImageCache { } // Get the image - let newImage = W3WImage(drawing: .mapSquare, colors: .standardMaps.with(background: color) .with(foreground: color.complimentaryTextColor())) + let newImage = W3WImage(drawing: .mapSquare, colors: .standard.with(background: color) .with(foreground: color.complimentaryTextColor())) .get(size: W3WIconSize(value: size)) // Cache it if it's not nil diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index 8959042..c1e3bf5 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -55,9 +55,9 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView bind() attachTapRecognizer() + } - /// Make an Apple Map Kit Map required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -81,7 +81,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView switch type { case .standard: return "standard" case .satellite: return "satellite" - case .hybrid: return "hybrid" + case .hybrid: return "hybridFlyover" default: return "hybridFlyover" } @@ -194,9 +194,6 @@ extension W3WAppleMapView: MKMapViewDelegate { public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { let currentMapScale = W3WMapScale(span: mapView.region.span, mapSize: mapView.frame.size) - if currentMapScale.value > transitionScale.value { - //update map - } w3wHelper.mapViewDidChangeVisibleRegion(mapView) viewModel.output.send(.camera(getCameraState())) @@ -230,17 +227,12 @@ extension W3WAppleMapView: MKMapViewDelegate { public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { - //test for zoomslevel - let mapRect = mapView.visibleMapRect - let mapWidthInPoints = mapView.frame.size.width - let zoomScale = mapRect.size.width / Double(mapWidthInPoints) - w3wHelper.mapView(mapView, regionDidChangeAnimated: animated) } public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { - + w3wHelper.mapView(mapView, mapTypeChanged: type) } //when marker is being selected From 84bb6127c4f2e84da190cc06e82aea208cfdb184 Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Thu, 8 May 2025 13:38:04 +0700 Subject: [PATCH 11/20] Update color of square and change color of grid line --- .../Drawing/W3WAppleMapDrawer.swift | 25 +++++++++++++------ .../Helper/W3WAppleMapHelper.swift | 17 +++++++------ .../Type/W3WImageCache.swift | 2 +- .../View/W3WAppleMapView.swift | 8 +++--- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift index 8601fb1..b2a884c 100644 --- a/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift +++ b/Sources/W3WSwiftComponentsMapApple/Drawing/W3WAppleMapDrawer.swift @@ -131,7 +131,7 @@ extension W3WAppleMapDrawerProtocol { if let gridLines = overlay as? W3WMapGridLines { mapGridData?.gridRenderer = W3WMapGridRenderer(multiPolyline: gridLines) mapGridData?.gridRenderer?.strokeColor = mapGridData?.scheme?.colors?.line?.uiColor - mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.7) + mapGridData?.gridRenderer?.lineWidth = mapGridData?.mapGridLineThickness.value.value ?? CGFloat(0.5) updateGridAlpha() return mapGridData?.gridRenderer } @@ -164,12 +164,11 @@ extension W3WAppleMapDrawerProtocol { w3wImage = W3WImageCache.shared.getImage(for: bgSquareColor ?? .w3wBrandBase, size: CGSize(width: 40, height: 40)) ?? W3WImageCache.shared.getImage(for: .w3wBrandBase, size: CGSize(width: 40, height: 40)) squareRenderer.strokeColor = mapGridData.scheme?.colors?.line?.uiColor - squareRenderer.lineWidth = mapGridData.mapSquareLineThickness.value.value - + squareRenderer.lineWidth = 0.1 + if (isSelectedSquare) { if (isMarker == true) { squareRenderer.lineWidth = 1.0 - if (isSquare == true) { // in list squareRenderer.setSquareImage(w3wImage) } @@ -179,9 +178,19 @@ extension W3WAppleMapDrawerProtocol { } } else { - - if (bgSquareColor != nil) { - squareRenderer.setSquareImage(w3wImage) + + if (isMarker == true) { + squareRenderer.strokeColor = mapGridData.scheme?.colors?.line?.uiColor + squareRenderer.lineWidth = 1.0 + + if (bgSquareColor != nil) { + squareRenderer.setSquareImage(w3wImage) + } + } + else{ + if (bgSquareColor != nil) { + squareRenderer.setSquareImage(w3wImage) + } } } @@ -344,7 +353,7 @@ extension W3WAppleMapDrawerProtocol { // If nothing has changed, skip the update if currentStateHash == gridData.previousStateHash && gridData.previousStateHash != 0 && gridData.squares.count != 0 { - return + // return } // Update hash for next comparison diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 451d9a4..df8929e 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -115,7 +115,7 @@ public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMap case .satellite: return "satellite" case .hybrid: return "hybridFlyover" - default: return "standard" + default: return "hybridFlyover" } } @@ -315,11 +315,11 @@ public extension W3WAppleMapHelper { } } else{ - removeSelectedSquare(at: selectedSquare) + removeSelectedSquare(at: selectedSquare) } let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - addSelectedMarker(at: at, color: nil, type: .square, isMarker: true, isMark: true) - self.mapGridData?.selectedSquare = at + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) + self.mapGridData?.selectedSquare = at return } @@ -350,9 +350,8 @@ public extension W3WAppleMapHelper { } } - let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - - addSelectedMarker(at: at, color: nil, type: .square, isMarker: true, isMark: true) + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) } self.mapGridData?.selectedSquare = at @@ -446,7 +445,7 @@ public extension W3WAppleMapHelper { if let gridData = self.mapGridData { gridData.squares.removeAll() gridData.markers.removeAll() - // gridData.selectedSquare = nil + // gridData.selectedSquare = nil // gridData.squareIsMarker = nil // gridData.currentSquare = nil gridData.overlayColors = [:] @@ -590,6 +589,7 @@ extension W3WAppleMapHelper { public func updateMarkers(markersLists: W3WMarkersLists) { // let newMarkers = self.getNewMarkers(markersLists: markersLists) self.removeAllMarkers() + select(at: self.mapGridData?.selectedSquare ?? W3WBaseSquare()) if !markersLists.getLists().isEmpty { for (_, list) in markersLists.getLists() { for marker in list.markers { @@ -597,6 +597,7 @@ extension W3WAppleMapHelper { } } } + } diff --git a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift index b5aacf2..bbcd210 100644 --- a/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift +++ b/Sources/W3WSwiftComponentsMapApple/Type/W3WImageCache.swift @@ -21,7 +21,7 @@ class W3WImageCache { } // Get the image - let newImage = W3WImage(drawing: .mapSquare, colors: .standard.with(background: color) .with(foreground: color.complimentaryTextColor())) + let newImage = W3WImage(drawing: .mapSquare, colors: .standardMaps.with(background: color) .with(foreground: color.complimentaryTextColor())) .get(size: W3WIconSize(value: size)) // Cache it if it's not nil diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index c1e3bf5..f167728 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -55,7 +55,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView bind() attachTapRecognizer() - + } /// Make an Apple Map Kit Map @@ -91,7 +91,6 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView let mapView = w3wHelper.mapView return W3WMapCamera(center: mapView?.region.center, scale: W3WMapScale(span: mapView!.region.span , mapSize: mapView!.frame.size)) - } public func set(scheme: W3WScheme?) { @@ -114,8 +113,7 @@ public class W3WAppleMapView: MKMapView, UIGestureRecognizerDelegate, W3WMapView subscribe(to: self.viewModel.input.camera) { [weak self] camera in guard let self = self else { return } - - w3wHelper.updateCamera(camera: camera) + w3wHelper.updateCamera(camera: camera) } subscribe(to: self.viewModel.input.selected) { [weak self] square in @@ -195,7 +193,7 @@ extension W3WAppleMapView: MKMapViewDelegate { let currentMapScale = W3WMapScale(span: mapView.region.span, mapSize: mapView.frame.size) w3wHelper.mapViewDidChangeVisibleRegion(mapView) - viewModel.output.send(.camera(getCameraState())) + // viewModel.output.send(.camera(getCameraState())) } From 655e81fe10d200a397facce9cfee25b4a3f036cb Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Thu, 8 May 2025 13:39:15 +0700 Subject: [PATCH 12/20] Update camera state after selecting the square --- Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift index f167728..00c35bc 100644 --- a/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift +++ b/Sources/W3WSwiftComponentsMapApple/View/W3WAppleMapView.swift @@ -193,7 +193,7 @@ extension W3WAppleMapView: MKMapViewDelegate { let currentMapScale = W3WMapScale(span: mapView.region.span, mapSize: mapView.frame.size) w3wHelper.mapViewDidChangeVisibleRegion(mapView) - // viewModel.output.send(.camera(getCameraState())) + viewModel.output.send(.camera(getCameraState())) } From b568a2d515e06d5772dcabee66ff065c963f2a26 Mon Sep 17 00:00:00 2001 From: Henry Ng Date: Tue, 13 May 2025 15:24:46 +0700 Subject: [PATCH 13/20] Update package info --- Package.resolved | 11 ++++++++++- Package.swift | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index 071e2e1..6a92c60 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "290e325529555297e0983cdcd68b712414da1d8a790e623a1eebe2334560ccc6", + "originHash" : "5f87e22a75273eac5ad2439f648625f1edf7573204cc78abbace21d06a81c5a8", "pins" : [ { "identity" : "w3w-swift-app-events", @@ -19,6 +19,15 @@ "revision" : "9823e608625044c42776a11b745c0ffc7b018021" } }, + { + "identity" : "w3w-swift-core", + "kind" : "remoteSourceControl", + "location" : "https://github.com/what3words/w3w-swift-core.git", + "state" : { + "branch" : "staging", + "revision" : "b00c40eeb249645138e705a3def58eee20f73fb0" + } + }, { "identity" : "w3w-swift-design", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 0220ce0..4e63755 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), .package(url: "https://github.com/what3words/w3w-swift-components-map.git", branch: "staging"), - .package(path: "../w3w-swift-core"), + .package(url: "https://github.com/what3words/w3w-swift-core.git", branch: "task/MT-7610-apple-map-view-package") ], targets: [ From 3070093237a19c0c3c1b31d2c5dcfa93f9f0fab4 Mon Sep 17 00:00:00 2001 From: Dave Duprey Date: Tue, 20 May 2025 12:39:33 +0700 Subject: [PATCH 14/20] changed dependancies to staging for dev --- Package.resolved | 4 ++-- Package.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Package.resolved b/Package.resolved index 6a92c60..92e4ccf 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5f87e22a75273eac5ad2439f648625f1edf7573204cc78abbace21d06a81c5a8", + "originHash" : "07879ecb841c90ffbeaeab1d6491eb5a39d5140d1aa8d8e8a55d4a5347473e87", "pins" : [ { "identity" : "w3w-swift-app-events", @@ -13,7 +13,7 @@ { "identity" : "w3w-swift-components-map", "kind" : "remoteSourceControl", - "location" : "https://github.com/what3words/w3w-swift-components-map.git", + "location" : "git@github.com:what3words/w3w-swift-components-map.git", "state" : { "branch" : "staging", "revision" : "9823e608625044c42776a11b745c0ffc7b018021" diff --git a/Package.swift b/Package.swift index 4e63755..f2fa706 100644 --- a/Package.swift +++ b/Package.swift @@ -13,10 +13,10 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), - .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), - .package(url: "https://github.com/what3words/w3w-swift-components-map.git", branch: "staging"), - .package(url: "https://github.com/what3words/w3w-swift-core.git", branch: "task/MT-7610-apple-map-view-package") + .package(url: "https://github.com/what3words/w3w-swift-themes.git", branch: "staging"), + .package(url: "https://github.com/what3words/w3w-swift-design.git", branch: "staging"), + .package(url: "git@github.com:what3words/w3w-swift-components-map.git", branch: "staging"), + .package(url: "https://github.com/what3words/w3w-swift-core.git", branch: "staging") ], targets: [ From c2a88134139edfc4d3708e16e6346590aa331838 Mon Sep 17 00:00:00 2001 From: Au Nguyen Date: Fri, 27 Jun 2025 10:20:51 +0700 Subject: [PATCH 15/20] MT-7776 Fix missing square markers in overlay when zoom in --- .../Helper/W3WAppleMapHelper.swift | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index df8929e..e479e95 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -586,21 +586,24 @@ extension W3WAppleMapHelper { return true } - public func updateMarkers(markersLists: W3WMarkersLists) { - // let newMarkers = self.getNewMarkers(markersLists: markersLists) - self.removeAllMarkers() - select(at: self.mapGridData?.selectedSquare ?? W3WBaseSquare()) - if !markersLists.getLists().isEmpty { - for (_, list) in markersLists.getLists() { - for marker in list.markers { - addMarker(at: marker, color: list.color, type: list.type ?? .circle) - } - } - } + public func updateMarkers(markersLists: W3WMarkersLists) { + removeAllMarkers() + select(at: mapGridData?.selectedSquare ?? W3WBaseSquare()) + + let defaultColor = W3WColor(light: .darkBlue, dark: .white) + let defaultType: W3WMarkerType = .circle + + let lists = markersLists.getLists() + + for list in lists.values { + for marker in list.markers { + let color = list.color ?? defaultColor + let type = list.type ?? defaultType + addMarker(at: marker, color: color, type: type) + } + } + } - } - - public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in From 4a1f699b4af1f6c6ef70ee9a3dff393a1dc3983f Mon Sep 17 00:00:00 2001 From: Au Nguyen Date: Fri, 27 Jun 2025 12:15:37 +0700 Subject: [PATCH 16/20] Reformat the code with standard indentation value --- .../Helper/W3WAppleMapHelper.swift | 1189 +++++++++-------- 1 file changed, 595 insertions(+), 594 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index e479e95..4e0d5ed 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -13,579 +13,579 @@ import W3WSwiftComponentsMap import W3WSwiftDesign public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMapHelperProtocol { - - public weak var mapView: MKMapView? - - public var mapGridData: W3WAppleMapGridData? - - public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() - - public var region: MKCoordinateRegion { - return mapView?.region ?? MKCoordinateRegion() - } - - public var annotations: [MKAnnotation] { - return mapView?.annotations ?? [MKAnnotation]() - } - - public var overlays: [MKOverlay] { - get { - return mapView?.overlays ?? [MKOverlay]() - } - } - - public var mapType: MKMapType { - get { - return mapView?.mapType as! MKMapType - } - set { - mapView?.mapType = newValue - self.redrawAll() - setGridColor() - } - } - - public var language: W3WLanguage = W3WSettings.defaultLanguage - - - /// called when the user taps a square in the map - public var onSquareSelected: (W3WSquare) -> () = { _ in } - - /// called when the user taps a square that has a marker added to it - public var onMarkerSelected: (W3WSquare) -> () = { _ in } - - private var w3w: W3WProtocolV4 - - public private(set) var markers: [W3WSquare] = [] - - public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { - self.mapView = mapView - self.w3w = w3w - super.init() - - self.mapGridData = W3WAppleMapGridData(w3w: w3w, scheme: scheme, language: language) - self.language = language - - } - - func setGridColor() { - if let gridData = mapGridData { - gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) - } - } - - - func setGridLineThickness(value: W3WLineThickness) { - if let gridData = mapGridData { - gridData.mapGridLineThickness.send(value) - } - } - - func setSquareLineThickness(value: W3WLineThickness) { - if let gridData = mapGridData { - gridData.mapSquareLineThickness.send(value) - } - } - - func configure () { - self.mapView?.showsUserLocation = true - } - - public func set(language: W3WLanguage) { - self.language = language - } - - public func set(type: String) { - switch type { - case "standard": self.mapType = .standard - case "hybrid": self.mapType = .hybrid - case "satellite": self.mapType = .satellite - - default: self.mapType = .standard - } - } - - public func set(scheme: W3WScheme?) { - self.mapGridData?.set(scheme: scheme) - } - - public func getType() -> W3WMapType { - switch self.mapType { - case .standard: return "standard" - case .satellite: return "satellite" - case .hybrid: return "hybridFlyover" - - default: return "hybridFlyover" - } - } - - public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { - updateMap() - // changeLineThicknessIfNeeded() - } - - /// hijack this delegate call and update the grid, then pass control to the external delegate - public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { - updateMap() - } - - /// hijack this delegate call and update the grid, then pass control to the external delegate - public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { - updateMap() - } - - public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { - updateMap() - } - - public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { - if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { - return a - } - - return nil - } - - public func addAnnotation(_ annotation: MKAnnotation) { - mapView?.addAnnotation(annotation) - } - - public func removeAnnotation(_ annotation: MKAnnotation) { - mapView?.removeAnnotation(annotation) - - } - - public func removeOverlay(_ overlay: MKOverlay) { - mapView?.removeOverlay(overlay) - } - - public func removeOverlays(_ overlays: [MKOverlay]) { - mapView?.removeOverlays(overlays) - } - - public func addOverlay(_ overlay: MKOverlay) { - mapView?.addOverlay(overlay) - } - - public func addOverlays(_ overlays: [MKOverlay]) { - mapView?.addOverlays(overlays) - } - - public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { - mapView?.addOverlays(overlays) - } - - public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { - - if let color = color, let square = overlay as? W3WMapSquareLines { - mapGridData?.overlayColors[square.box.id] = color - } - mapView?.addOverlay(overlay) - -} - - public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { - if let markerView = view.annotation as? W3WAppleMapAnnotation { - } - } - - public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { - - } - - public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { - // Maintain a dictionary of positions by section - var positionsBySection = [String: [CGPoint]]() - - // Minimum distance between pins in points - let minDistance: CGFloat = 5.0 - - for view in views { - guard let annotation = view.annotation as? W3WAppleMapAnnotation else { continue } - - // Create a section key based on annotation's location - // Round to nearest grid to group nearby pins - let gridSize: Double = 0.0001 // Adjust based on your needs - let latValue: Double = annotation.square?.coordinates?.latitude ?? annotation.coordinate.latitude - let lngValue: Double = annotation.square?.coordinates?.longitude ?? annotation.coordinate.longitude - - let latSection = Int(latValue / gridSize) - let lngSection = Int(lngValue / gridSize) - let sectionKey = "\(latSection)_\(lngSection)" - - // Get current center point for this view - let center = view.center - - // Get existing positions in this section - var positions = positionsBySection[sectionKey] ?? [] - - // Check if this position is too close to existing ones - var needsAdjustment = false - for existingPos in positions { - let distance = hypot(center.x - existingPos.x, center.y - existingPos.y) - if distance < minDistance { - needsAdjustment = true - break - } - } - - // If too close, apply a small offset - if needsAdjustment { - // Calculate offset direction (try to avoid overlaps) - let offsetX = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 - let offsetY = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 - - // Apply offset - view.centerOffset = CGPoint( - x: view.centerOffset.x + offsetX, - y: view.centerOffset.y + offsetY - ) - } - - // Add this position to our tracking dictionary - positions.append(view.center) - positionsBySection[sectionKey] = positions - } - } - + + public weak var mapView: MKMapView? + + public var mapGridData: W3WAppleMapGridData? + + public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() + + public var region: MKCoordinateRegion { + return mapView?.region ?? MKCoordinateRegion() + } + + public var annotations: [MKAnnotation] { + return mapView?.annotations ?? [MKAnnotation]() + } + + public var overlays: [MKOverlay] { + get { + return mapView?.overlays ?? [MKOverlay]() + } + } + + public var mapType: MKMapType { + get { + return mapView?.mapType as! MKMapType + } + set { + mapView?.mapType = newValue + self.redrawAll() + setGridColor() + } + } + + public var language: W3WLanguage = W3WSettings.defaultLanguage + + + /// called when the user taps a square in the map + public var onSquareSelected: (W3WSquare) -> () = { _ in } + + /// called when the user taps a square that has a marker added to it + public var onMarkerSelected: (W3WSquare) -> () = { _ in } + + private var w3w: W3WProtocolV4 + + public private(set) var markers: [W3WSquare] = [] + + public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { + self.mapView = mapView + self.w3w = w3w + super.init() + + self.mapGridData = W3WAppleMapGridData(w3w: w3w, scheme: scheme, language: language) + self.language = language + + } + + func setGridColor() { + if let gridData = mapGridData { + gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) + } + } + + + func setGridLineThickness(value: W3WLineThickness) { + if let gridData = mapGridData { + gridData.mapGridLineThickness.send(value) + } + } + + func setSquareLineThickness(value: W3WLineThickness) { + if let gridData = mapGridData { + gridData.mapSquareLineThickness.send(value) + } + } + + func configure () { + self.mapView?.showsUserLocation = true + } + + public func set(language: W3WLanguage) { + self.language = language + } + + public func set(type: String) { + switch type { + case "standard": self.mapType = .standard + case "hybrid": self.mapType = .hybrid + case "satellite": self.mapType = .satellite + + default: self.mapType = .standard + } + } + + public func set(scheme: W3WScheme?) { + self.mapGridData?.set(scheme: scheme) + } + + public func getType() -> W3WMapType { + switch self.mapType { + case .standard: return "standard" + case .satellite: return "satellite" + case .hybrid: return "hybridFlyover" + + default: return "hybridFlyover" + } + } + + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + updateMap() + // changeLineThicknessIfNeeded() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + updateMap() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { + if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { + return a + } + + return nil + } + + public func addAnnotation(_ annotation: MKAnnotation) { + mapView?.addAnnotation(annotation) + } + + public func removeAnnotation(_ annotation: MKAnnotation) { + mapView?.removeAnnotation(annotation) + + } + + public func removeOverlay(_ overlay: MKOverlay) { + mapView?.removeOverlay(overlay) + } + + public func removeOverlays(_ overlays: [MKOverlay]) { + mapView?.removeOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay) { + mapView?.addOverlay(overlay) + } + + public func addOverlays(_ overlays: [MKOverlay]) { + mapView?.addOverlays(overlays) + } + + public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { + mapView?.addOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { + + if let color = color, let square = overlay as? W3WMapSquareLines { + mapGridData?.overlayColors[square.box.id] = color + } + mapView?.addOverlay(overlay) + + } + + public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + if let markerView = view.annotation as? W3WAppleMapAnnotation { + } + } + + public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { + + } + + public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { + // Maintain a dictionary of positions by section + var positionsBySection = [String: [CGPoint]]() + + // Minimum distance between pins in points + let minDistance: CGFloat = 5.0 + + for view in views { + guard let annotation = view.annotation as? W3WAppleMapAnnotation else { continue } + + // Create a section key based on annotation's location + // Round to nearest grid to group nearby pins + let gridSize: Double = 0.0001 // Adjust based on your needs + let latValue: Double = annotation.square?.coordinates?.latitude ?? annotation.coordinate.latitude + let lngValue: Double = annotation.square?.coordinates?.longitude ?? annotation.coordinate.longitude + + let latSection = Int(latValue / gridSize) + let lngSection = Int(lngValue / gridSize) + let sectionKey = "\(latSection)_\(lngSection)" + + // Get current center point for this view + let center = view.center + + // Get existing positions in this section + var positions = positionsBySection[sectionKey] ?? [] + + // Check if this position is too close to existing ones + var needsAdjustment = false + for existingPos in positions { + let distance = hypot(center.x - existingPos.x, center.y - existingPos.y) + if distance < minDistance { + needsAdjustment = true + break + } + } + + // If too close, apply a small offset + if needsAdjustment { + // Calculate offset direction (try to avoid overlaps) + let offsetX = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + let offsetY = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + + // Apply offset + view.centerOffset = CGPoint( + x: view.centerOffset.x + offsetX, + y: view.centerOffset.y + offsetY + ) + } + + // Add this position to our tracking dictionary + positions.append(view.center) + positionsBySection[sectionKey] = positions + } + } + } public extension W3WAppleMapHelper { - - func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { - - self.convertTo3wa(coordinates: coordinates, language: self.language) { [weak self] square, error in - - guard let self = self else { return } - - if let e = error { - W3WThread.runOnMain { - self.mapGridData?.onError(e) - completion(.failure(e)) - } - } - if let s = square { - W3WThread.runOnMain { - // self.select(at: s) - completion(.success(s)) - } - } else { - W3WThread.runOnMain { - let e = W3WError.message("No Square Found") - completion(.failure(e)) - } - } - } - } - - func select(at: W3WSquare) { - createMarkerForConditions(at) - } - - func createMarkerForConditions(_ at: W3WSquare) { - - let squares = self.mapGridData?.squares - - let selectedSquare = self.mapGridData?.selectedSquare - - let isMarkerinList = squares?.contains(where: { $0.bounds?.id == at.bounds?.id }) - - let isPrevMarkerinList = squares?.contains(where: { $0.bounds?.id == selectedSquare?.bounds?.id }) - - let annotation = findAnnotation(selectedSquare) - - let markers = self.mapGridData?.markers - - let squareSize = getPointsPerSquare() - - if let selectedSquare = selectedSquare { - if squareSize < self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { - if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square - let previousBoxId = selectedSquare.bounds?.id - - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - removeSelectedSquare(at: selectedSquare) - addMarkerAsCircle(at: selectedSquare, color: previousColor) - } - else{ - if markers != nil { - removeSelectedSquare(at: selectedSquare) - addMarkerAsCircle(at: selectedSquare, color: annotation?.color) - } - } - } - else{ - removeSelectedSquare(at: selectedSquare) - } - let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) - self.mapGridData?.selectedSquare = at - - return - } - removeSelectedSquare(at: selectedSquare) - } - - //squares - if isMarkerinList == true { - removeSelectedSquare(at: selectedSquare) - - let currentBoxId = at.bounds?.id - let previousBoxId = selectedSquare?.bounds?.id - if isPrevMarkerinList == true { - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - addMarker(at: selectedSquare, color: previousColor, type: .circle) - } - } - if let color = self.mapGridData?.overlayColors[currentBoxId ?? 0] { - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: false) - } - self.mapGridData?.squareIsMarker = at - - } else { - let previousBoxId = selectedSquare?.bounds?.id - if isPrevMarkerinList == true { - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - addMarkerAsCircle(at: selectedSquare, color: previousColor) - } - } - - let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) - } - - self.mapGridData?.selectedSquare = at - } - - /// put a what3words annotation on the map showing the address - func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: square, color: color, type: type, completion: completion) - } - - func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: suggestion, color: color, type: type, completion: completion) - } - - func addMarker(at word: String?, color: W3WSwiftThemes.W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - - addMarker(at: word, color: color, type: type, completion: completion) - } - - func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: words, color: color, type: type, completion: completion) - } - - func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: coordinate, color: color, type: type, completion: completion) - } - - func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: squares, color: color, type: type, completion: completion) - } - - func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: suggestions, color: color, type: type, completion: completion) - } - - func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: coordinates, color: color, type: type, completion: completion) - } - - func removeMarker(at suggestion: W3WSuggestion?) { - - } - - func removeMarker(at words: String?) { - - } - - func removeMarker(at squares: [W3WSquare]?) { - - } - - func removeMarker(at suggestions: [W3WSuggestion]?) { - - } - - func removeMarker(at words: [String]?) { - - } - - func removeMarker(at square: W3WSquare?) { - // removeMarker(at: square) - } - - func removeMarker(group: String) { - - } - - func unselect() { - - } - - func hover(at: CLLocationCoordinate2D) { - - } - - func unhover() { - - } - - func set(zoomInPointsPerSquare: CGFloat) { - - } - - func getAllMarkers() -> [W3WSquare] { - return [W3WSquare]() - } - - func removeAllMarkers() { - self.markers.removeAll() - - if let gridData = self.mapGridData { - gridData.squares.removeAll() - gridData.markers.removeAll() - // gridData.selectedSquare = nil - // gridData.squareIsMarker = nil - // gridData.currentSquare = nil - gridData.overlayColors = [:] - gridData.previousStateHash = nil - - for annotation in annotations { - removeAnnotation(annotation) - } - } - } - - func redraw(){ - redrawGrid() - redrawSquares() - redrawPins() - } - - func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { - return nil - } + + func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { + + self.convertTo3wa(coordinates: coordinates, language: self.language) { [weak self] square, error in + + guard let self = self else { return } + + if let e = error { + W3WThread.runOnMain { + self.mapGridData?.onError(e) + completion(.failure(e)) + } + } + if let s = square { + W3WThread.runOnMain { + // self.select(at: s) + completion(.success(s)) + } + } else { + W3WThread.runOnMain { + let e = W3WError.message("No Square Found") + completion(.failure(e)) + } + } + } + } + + func select(at: W3WSquare) { + createMarkerForConditions(at) + } + + func createMarkerForConditions(_ at: W3WSquare) { + + let squares = self.mapGridData?.squares + + let selectedSquare = self.mapGridData?.selectedSquare + + let isMarkerinList = squares?.contains(where: { $0.bounds?.id == at.bounds?.id }) + + let isPrevMarkerinList = squares?.contains(where: { $0.bounds?.id == selectedSquare?.bounds?.id }) + + let annotation = findAnnotation(selectedSquare) + + let markers = self.mapGridData?.markers + + let squareSize = getPointsPerSquare() + + if let selectedSquare = selectedSquare { + if squareSize < self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { + if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square + let previousBoxId = selectedSquare.bounds?.id + + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: previousColor) + } + else{ + if markers != nil { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: annotation?.color) + } + } + } + else{ + removeSelectedSquare(at: selectedSquare) + } + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) + self.mapGridData?.selectedSquare = at + + return + } + removeSelectedSquare(at: selectedSquare) + } + + //squares + if isMarkerinList == true { + removeSelectedSquare(at: selectedSquare) + + let currentBoxId = at.bounds?.id + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarker(at: selectedSquare, color: previousColor, type: .circle) + } + } + if let color = self.mapGridData?.overlayColors[currentBoxId ?? 0] { + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: false) + } + self.mapGridData?.squareIsMarker = at + + } else { + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarkerAsCircle(at: selectedSquare, color: previousColor) + } + } + + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) + } + + self.mapGridData?.selectedSquare = at + } + + /// put a what3words annotation on the map showing the address + func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: square, color: color, type: type, completion: completion) + } + + func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestion, color: color, type: type, completion: completion) + } + + func addMarker(at word: String?, color: W3WSwiftThemes.W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + + addMarker(at: word, color: color, type: type, completion: completion) + } + + func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: words, color: color, type: type, completion: completion) + } + + func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinate, color: color, type: type, completion: completion) + } + + func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: squares, color: color, type: type, completion: completion) + } + + func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestions, color: color, type: type, completion: completion) + } + + func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinates, color: color, type: type, completion: completion) + } + + func removeMarker(at suggestion: W3WSuggestion?) { + + } + + func removeMarker(at words: String?) { + + } + + func removeMarker(at squares: [W3WSquare]?) { + + } + + func removeMarker(at suggestions: [W3WSuggestion]?) { + + } + + func removeMarker(at words: [String]?) { + + } + + func removeMarker(at square: W3WSquare?) { + // removeMarker(at: square) + } + + func removeMarker(group: String) { + + } + + func unselect() { + + } + + func hover(at: CLLocationCoordinate2D) { + + } + + func unhover() { + + } + + func set(zoomInPointsPerSquare: CGFloat) { + + } + + func getAllMarkers() -> [W3WSquare] { + return [W3WSquare]() + } + + func removeAllMarkers() { + self.markers.removeAll() + + if let gridData = self.mapGridData { + gridData.squares.removeAll() + gridData.markers.removeAll() + // gridData.selectedSquare = nil + // gridData.squareIsMarker = nil + // gridData.currentSquare = nil + gridData.overlayColors = [:] + gridData.previousStateHash = nil + + for annotation in annotations { + removeAnnotation(annotation) + } + } + } + + func redraw(){ + redrawGrid() + redrawSquares() + redrawPins() + } + + func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { + return nil + } } extension W3WAppleMapHelper { - - public func updateCamera(camera: W3WMapCamera?) { - - W3WThread.runOnMain { [weak self] in - if let self = self { - if let center = camera?.center, let scale = camera?.scale { - let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) - mapView?.setRegion(region, animated: true) - - } else if let center = camera?.center { - mapView?.setCenter(center, animated: true) - - } else if let scale = camera?.scale { - let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) - mapView?.setRegion(region, animated: true) - } - } - } - } - - public func updateSquare(square: W3WSquare?) { - if let square = square { - self.mapGridData?.currentSquare = square - self.select(at: square) - } - } - - private func getNewMarkers(markersLists: W3WMarkersLists) -> W3WMarkersLists { - guard let gridData = self.mapGridData else { - return W3WMarkersLists() - } - - // Create a new markers list to return - let newMarkersLists = W3WMarkersLists() - - // Clear the automatically created default list - newMarkersLists.lists.removeAll() - - // Process each list in the input - for (listName, list) in markersLists.getLists() { - // Skip empty lists or default list with no color - if list.markers.isEmpty || (listName == "default" && list.color == nil) { - continue - } - - // Create a new list for this color - let newList = W3WMarkerList() - newList.color = list.color - newList.type = list.type - - // Track already processed squares for this specific list - var listProcessedIds = Set() - - for marker in list.markers { - if let bounds = marker.bounds { - let squareId = bounds.id - - // Skip if we've already processed this ID in this list - if listProcessedIds.contains(squareId) { - continue - } - - // Check if this square exists in overlay colors - if let existingColor = gridData.overlayColors[squareId] { - // Only include if the color is different - if let listColor = list.color, !colorComponentsMatch(existingColor.cgColor, listColor.cgColor) { - newList.markers.append(marker) - } - } else { - // Square doesn't exist in overlay colors, so include it - newList.markers.append(marker) - } - - // Mark this ID as processed for this list - listProcessedIds.insert(squareId) - } else { - // No bounds, include it - newList.markers.append(marker) - } - } - - // Only add lists with markers - if !newList.markers.isEmpty { - newMarkersLists.add(listName: listName, list: newList) - } - } - - return newMarkersLists - } - - // Helper function to compare color components - private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { - // Check if color spaces match - guard color1.colorSpace?.model == color2.colorSpace?.model else { - return false - } - - // Get components - let components1 = color1.components ?? [] - let components2 = color2.components ?? [] - - // Check if component counts match - guard components1.count == components2.count else { - return false - } - - // Compare components with a small tolerance - let tolerance: CGFloat = 0.001 - for i in 0.. tolerance { - return false - } - } - - return true - } - + + public func updateCamera(camera: W3WMapCamera?) { + + W3WThread.runOnMain { [weak self] in + if let self = self { + if let center = camera?.center, let scale = camera?.scale { + let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) + mapView?.setRegion(region, animated: true) + + } else if let center = camera?.center { + mapView?.setCenter(center, animated: true) + + } else if let scale = camera?.scale { + let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) + mapView?.setRegion(region, animated: true) + } + } + } + } + + public func updateSquare(square: W3WSquare?) { + if let square = square { + self.mapGridData?.currentSquare = square + self.select(at: square) + } + } + + private func getNewMarkers(markersLists: W3WMarkersLists) -> W3WMarkersLists { + guard let gridData = self.mapGridData else { + return W3WMarkersLists() + } + + // Create a new markers list to return + let newMarkersLists = W3WMarkersLists() + + // Clear the automatically created default list + newMarkersLists.lists.removeAll() + + // Process each list in the input + for (listName, list) in markersLists.getLists() { + // Skip empty lists or default list with no color + if list.markers.isEmpty || (listName == "default" && list.color == nil) { + continue + } + + // Create a new list for this color + let newList = W3WMarkerList() + newList.color = list.color + newList.type = list.type + + // Track already processed squares for this specific list + var listProcessedIds = Set() + + for marker in list.markers { + if let bounds = marker.bounds { + let squareId = bounds.id + + // Skip if we've already processed this ID in this list + if listProcessedIds.contains(squareId) { + continue + } + + // Check if this square exists in overlay colors + if let existingColor = gridData.overlayColors[squareId] { + // Only include if the color is different + if let listColor = list.color, !colorComponentsMatch(existingColor.cgColor, listColor.cgColor) { + newList.markers.append(marker) + } + } else { + // Square doesn't exist in overlay colors, so include it + newList.markers.append(marker) + } + + // Mark this ID as processed for this list + listProcessedIds.insert(squareId) + } else { + // No bounds, include it + newList.markers.append(marker) + } + } + + // Only add lists with markers + if !newList.markers.isEmpty { + newMarkersLists.add(listName: listName, list: newList) + } + } + + return newMarkersLists + } + + // Helper function to compare color components + private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { + // Check if color spaces match + guard color1.colorSpace?.model == color2.colorSpace?.model else { + return false + } + + // Get components + let components1 = color1.components ?? [] + let components2 = color2.components ?? [] + + // Check if component counts match + guard components1.count == components2.count else { + return false + } + + // Compare components with a small tolerance + let tolerance: CGFloat = 0.001 + for i in 0.. tolerance { + return false + } + } + + return true + } + public func updateMarkers(markersLists: W3WMarkersLists) { removeAllMarkers() select(at: mapGridData?.selectedSquare ?? W3WBaseSquare()) @@ -596,39 +596,40 @@ extension W3WAppleMapHelper { let lists = markersLists.getLists() for list in lists.values { + let color = list.color ?? defaultColor + let type = list.type ?? defaultType + for marker in list.markers { - let color = list.color ?? defaultColor - let type = list.type ?? defaultType addMarker(at: marker, color: color, type: type) } } } - - public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { - - self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in - guard self != nil else { return } - - if let error = error { - W3WThread.runOnMain { - completion(nil, error) - } - } else if let s = square { - W3WThread.runOnMain { - completion(s, nil) - } - } - } - } + + public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { + + self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in + guard self != nil else { return } + + if let error = error { + W3WThread.runOnMain { + completion(nil, error) + } + } else if let s = square { + W3WThread.runOnMain { + completion(s, nil) + } + } + } + } } extension W3WAppleMapHelper { - - public func setRegion(_ region: MKCoordinateRegion, animated: Bool) { - mapView?.setRegion(region, animated: false) - } - - public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { - mapView?.setCenter(coordinate, animated: animated) - } + + public func setRegion(_ region: MKCoordinateRegion, animated: Bool) { + mapView?.setRegion(region, animated: false) + } + + public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { + mapView?.setCenter(coordinate, animated: animated) + } } From b3a23818fdfbace228ff2d6238fc92197c6debcb Mon Sep 17 00:00:00 2001 From: Au Nguyen Date: Fri, 27 Jun 2025 13:20:32 +0700 Subject: [PATCH 17/20] Using space indentation instead of tab --- .../Helper/W3WAppleMapHelper.swift | 1220 ++++++++--------- 1 file changed, 610 insertions(+), 610 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index 4e0d5ed..c5ef676 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -13,623 +13,623 @@ import W3WSwiftComponentsMap import W3WSwiftDesign public class W3WAppleMapHelper: NSObject, W3WAppleMapDrawerProtocol, W3WAppleMapHelperProtocol { - - public weak var mapView: MKMapView? - - public var mapGridData: W3WAppleMapGridData? - - public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() - - public var region: MKCoordinateRegion { - return mapView?.region ?? MKCoordinateRegion() - } - - public var annotations: [MKAnnotation] { - return mapView?.annotations ?? [MKAnnotation]() - } - - public var overlays: [MKOverlay] { - get { - return mapView?.overlays ?? [MKOverlay]() - } - } - - public var mapType: MKMapType { - get { - return mapView?.mapType as! MKMapType - } - set { - mapView?.mapType = newValue - self.redrawAll() - setGridColor() - } - } - - public var language: W3WLanguage = W3WSettings.defaultLanguage - - - /// called when the user taps a square in the map - public var onSquareSelected: (W3WSquare) -> () = { _ in } - - /// called when the user taps a square that has a marker added to it - public var onMarkerSelected: (W3WSquare) -> () = { _ in } - - private var w3w: W3WProtocolV4 - - public private(set) var markers: [W3WSquare] = [] - - public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { - self.mapView = mapView - self.w3w = w3w - super.init() - - self.mapGridData = W3WAppleMapGridData(w3w: w3w, scheme: scheme, language: language) - self.language = language - - } - - func setGridColor() { - if let gridData = mapGridData { - gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) - } - } - - - func setGridLineThickness(value: W3WLineThickness) { - if let gridData = mapGridData { - gridData.mapGridLineThickness.send(value) - } - } - - func setSquareLineThickness(value: W3WLineThickness) { - if let gridData = mapGridData { - gridData.mapSquareLineThickness.send(value) - } - } - - func configure () { - self.mapView?.showsUserLocation = true - } - - public func set(language: W3WLanguage) { - self.language = language - } - - public func set(type: String) { - switch type { - case "standard": self.mapType = .standard - case "hybrid": self.mapType = .hybrid - case "satellite": self.mapType = .satellite - - default: self.mapType = .standard - } - } - - public func set(scheme: W3WScheme?) { - self.mapGridData?.set(scheme: scheme) - } - - public func getType() -> W3WMapType { - switch self.mapType { - case .standard: return "standard" - case .satellite: return "satellite" - case .hybrid: return "hybridFlyover" - - default: return "hybridFlyover" - } - } - - public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { - updateMap() - // changeLineThicknessIfNeeded() - } - - /// hijack this delegate call and update the grid, then pass control to the external delegate - public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { - updateMap() - } - - /// hijack this delegate call and update the grid, then pass control to the external delegate - public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { - updateMap() - } - - public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { - updateMap() - } - - public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { - if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { - return a - } - - return nil - } - - public func addAnnotation(_ annotation: MKAnnotation) { - mapView?.addAnnotation(annotation) - } - - public func removeAnnotation(_ annotation: MKAnnotation) { - mapView?.removeAnnotation(annotation) - - } - - public func removeOverlay(_ overlay: MKOverlay) { - mapView?.removeOverlay(overlay) - } - - public func removeOverlays(_ overlays: [MKOverlay]) { - mapView?.removeOverlays(overlays) - } - - public func addOverlay(_ overlay: MKOverlay) { - mapView?.addOverlay(overlay) - } - - public func addOverlays(_ overlays: [MKOverlay]) { - mapView?.addOverlays(overlays) - } - - public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { - mapView?.addOverlays(overlays) - } - - public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { - - if let color = color, let square = overlay as? W3WMapSquareLines { - mapGridData?.overlayColors[square.box.id] = color - } - mapView?.addOverlay(overlay) - - } - - public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { - if let markerView = view.annotation as? W3WAppleMapAnnotation { - } - } - - public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { - - } - - public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { - // Maintain a dictionary of positions by section - var positionsBySection = [String: [CGPoint]]() - - // Minimum distance between pins in points - let minDistance: CGFloat = 5.0 - - for view in views { - guard let annotation = view.annotation as? W3WAppleMapAnnotation else { continue } - - // Create a section key based on annotation's location - // Round to nearest grid to group nearby pins - let gridSize: Double = 0.0001 // Adjust based on your needs - let latValue: Double = annotation.square?.coordinates?.latitude ?? annotation.coordinate.latitude - let lngValue: Double = annotation.square?.coordinates?.longitude ?? annotation.coordinate.longitude - - let latSection = Int(latValue / gridSize) - let lngSection = Int(lngValue / gridSize) - let sectionKey = "\(latSection)_\(lngSection)" - - // Get current center point for this view - let center = view.center - - // Get existing positions in this section - var positions = positionsBySection[sectionKey] ?? [] - - // Check if this position is too close to existing ones - var needsAdjustment = false - for existingPos in positions { - let distance = hypot(center.x - existingPos.x, center.y - existingPos.y) - if distance < minDistance { - needsAdjustment = true - break - } - } - - // If too close, apply a small offset - if needsAdjustment { - // Calculate offset direction (try to avoid overlaps) - let offsetX = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 - let offsetY = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 - - // Apply offset - view.centerOffset = CGPoint( - x: view.centerOffset.x + offsetX, - y: view.centerOffset.y + offsetY - ) - } - - // Add this position to our tracking dictionary - positions.append(view.center) - positionsBySection[sectionKey] = positions - } - } - + + public weak var mapView: MKMapView? + + public var mapGridData: W3WAppleMapGridData? + + public var scheme: W3WScheme? = W3WTheme.what3words.mapScheme() + + public var region: MKCoordinateRegion { + return mapView?.region ?? MKCoordinateRegion() + } + + public var annotations: [MKAnnotation] { + return mapView?.annotations ?? [MKAnnotation]() + } + + public var overlays: [MKOverlay] { + get { + return mapView?.overlays ?? [MKOverlay]() + } + } + + public var mapType: MKMapType { + get { + return mapView?.mapType as! MKMapType + } + set { + mapView?.mapType = newValue + self.redrawAll() + setGridColor() + } + } + + public var language: W3WLanguage = W3WSettings.defaultLanguage + + + /// called when the user taps a square in the map + public var onSquareSelected: (W3WSquare) -> () = { _ in } + + /// called when the user taps a square that has a marker added to it + public var onMarkerSelected: (W3WSquare) -> () = { _ in } + + private var w3w: W3WProtocolV4 + + public private(set) var markers: [W3WSquare] = [] + + public init(mapView: MKMapView, _ w3w: W3WProtocolV4, language: W3WLanguage = W3WSettings.defaultLanguage ) { + self.mapView = mapView + self.w3w = w3w + super.init() + + self.mapGridData = W3WAppleMapGridData(w3w: w3w, scheme: scheme, language: language) + self.language = language + + } + + func setGridColor() { + if let gridData = mapGridData { + gridData.mapGridColor.send(mapType == .standard ? .mediumGrey : .white) + } + } + + + func setGridLineThickness(value: W3WLineThickness) { + if let gridData = mapGridData { + gridData.mapGridLineThickness.send(value) + } + } + + func setSquareLineThickness(value: W3WLineThickness) { + if let gridData = mapGridData { + gridData.mapSquareLineThickness.send(value) + } + } + + func configure () { + self.mapView?.showsUserLocation = true + } + + public func set(language: W3WLanguage) { + self.language = language + } + + public func set(type: String) { + switch type { + case "standard": self.mapType = .standard + case "hybrid": self.mapType = .hybrid + case "satellite": self.mapType = .satellite + + default: self.mapType = .standard + } + } + + public func set(scheme: W3WScheme?) { + self.mapGridData?.set(scheme: scheme) + } + + public func getType() -> W3WMapType { + switch self.mapType { + case .standard: return "standard" + case .satellite: return "satellite" + case .hybrid: return "hybridFlyover" + + default: return "hybridFlyover" + } + } + + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + updateMap() + // changeLineThicknessIfNeeded() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + updateMap() + } + + /// hijack this delegate call and update the grid, then pass control to the external delegate + public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, mapTypeChanged type: MKMapType) { + updateMap() + } + + public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation, with transitionScale: CGFloat) -> MKAnnotationView? { + if let a = getMapAnnotationView(annotation: annotation, transitionScale: transitionScale) { + return a + } + + return nil + } + + public func addAnnotation(_ annotation: MKAnnotation) { + mapView?.addAnnotation(annotation) + } + + public func removeAnnotation(_ annotation: MKAnnotation) { + mapView?.removeAnnotation(annotation) + + } + + public func removeOverlay(_ overlay: MKOverlay) { + mapView?.removeOverlay(overlay) + } + + public func removeOverlays(_ overlays: [MKOverlay]) { + mapView?.removeOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay) { + mapView?.addOverlay(overlay) + } + + public func addOverlays(_ overlays: [MKOverlay]) { + mapView?.addOverlays(overlays) + } + + public func addOverlays(_ overlays: [MKOverlay], _ color: W3WColor?) { + mapView?.addOverlays(overlays) + } + + public func addOverlay(_ overlay: MKOverlay, _ color: W3WColor? = nil) { + + if let color = color, let square = overlay as? W3WMapSquareLines { + mapGridData?.overlayColors[square.box.id] = color + } + mapView?.addOverlay(overlay) + + } + + public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + if let markerView = view.annotation as? W3WAppleMapAnnotation { + } + } + + public func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) { + + } + + public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { + // Maintain a dictionary of positions by section + var positionsBySection = [String: [CGPoint]]() + + // Minimum distance between pins in points + let minDistance: CGFloat = 5.0 + + for view in views { + guard let annotation = view.annotation as? W3WAppleMapAnnotation else { continue } + + // Create a section key based on annotation's location + // Round to nearest grid to group nearby pins + let gridSize: Double = 0.0001 // Adjust based on your needs + let latValue: Double = annotation.square?.coordinates?.latitude ?? annotation.coordinate.latitude + let lngValue: Double = annotation.square?.coordinates?.longitude ?? annotation.coordinate.longitude + + let latSection = Int(latValue / gridSize) + let lngSection = Int(lngValue / gridSize) + let sectionKey = "\(latSection)_\(lngSection)" + + // Get current center point for this view + let center = view.center + + // Get existing positions in this section + var positions = positionsBySection[sectionKey] ?? [] + + // Check if this position is too close to existing ones + var needsAdjustment = false + for existingPos in positions { + let distance = hypot(center.x - existingPos.x, center.y - existingPos.y) + if distance < minDistance { + needsAdjustment = true + break + } + } + + // If too close, apply a small offset + if needsAdjustment { + // Calculate offset direction (try to avoid overlaps) + let offsetX = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + let offsetY = CGFloat(arc4random_uniform(2) == 0 ? -1 : 1) * minDistance * 0.7 + + // Apply offset + view.centerOffset = CGPoint( + x: view.centerOffset.x + offsetX, + y: view.centerOffset.y + offsetY + ) + } + + // Add this position to our tracking dictionary + positions.append(view.center) + positionsBySection[sectionKey] = positions + } + } + } public extension W3WAppleMapHelper { - - func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { - - self.convertTo3wa(coordinates: coordinates, language: self.language) { [weak self] square, error in - - guard let self = self else { return } - - if let e = error { - W3WThread.runOnMain { - self.mapGridData?.onError(e) - completion(.failure(e)) - } - } - if let s = square { - W3WThread.runOnMain { - // self.select(at: s) - completion(.success(s)) - } - } else { - W3WThread.runOnMain { - let e = W3WError.message("No Square Found") - completion(.failure(e)) - } - } - } - } - - func select(at: W3WSquare) { - createMarkerForConditions(at) - } - - func createMarkerForConditions(_ at: W3WSquare) { - - let squares = self.mapGridData?.squares - - let selectedSquare = self.mapGridData?.selectedSquare - - let isMarkerinList = squares?.contains(where: { $0.bounds?.id == at.bounds?.id }) - - let isPrevMarkerinList = squares?.contains(where: { $0.bounds?.id == selectedSquare?.bounds?.id }) - - let annotation = findAnnotation(selectedSquare) - - let markers = self.mapGridData?.markers - - let squareSize = getPointsPerSquare() - - if let selectedSquare = selectedSquare { - if squareSize < self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { - if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square - let previousBoxId = selectedSquare.bounds?.id - - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - removeSelectedSquare(at: selectedSquare) - addMarkerAsCircle(at: selectedSquare, color: previousColor) - } - else{ - if markers != nil { - removeSelectedSquare(at: selectedSquare) - addMarkerAsCircle(at: selectedSquare, color: annotation?.color) - } - } - } - else{ - removeSelectedSquare(at: selectedSquare) - } - let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) - self.mapGridData?.selectedSquare = at - - return - } - removeSelectedSquare(at: selectedSquare) - } - - //squares - if isMarkerinList == true { - removeSelectedSquare(at: selectedSquare) - - let currentBoxId = at.bounds?.id - let previousBoxId = selectedSquare?.bounds?.id - if isPrevMarkerinList == true { - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - addMarker(at: selectedSquare, color: previousColor, type: .circle) - } - } - if let color = self.mapGridData?.overlayColors[currentBoxId ?? 0] { - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: false) - } - self.mapGridData?.squareIsMarker = at - - } else { - let previousBoxId = selectedSquare?.bounds?.id - if isPrevMarkerinList == true { - if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { - addMarkerAsCircle(at: selectedSquare, color: previousColor) - } - } - - let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black - addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) - } - - self.mapGridData?.selectedSquare = at - } - - /// put a what3words annotation on the map showing the address - func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: square, color: color, type: type, completion: completion) - } - - func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: suggestion, color: color, type: type, completion: completion) - } - - func addMarker(at word: String?, color: W3WSwiftThemes.W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - - addMarker(at: word, color: color, type: type, completion: completion) - } - - func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: words, color: color, type: type, completion: completion) - } - - func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: coordinate, color: color, type: type, completion: completion) - } - - func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: squares, color: color, type: type, completion: completion) - } - - func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: suggestions, color: color, type: type, completion: completion) - } - - func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { - addMarker(at: coordinates, color: color, type: type, completion: completion) - } - - func removeMarker(at suggestion: W3WSuggestion?) { - - } - - func removeMarker(at words: String?) { - - } - - func removeMarker(at squares: [W3WSquare]?) { - - } - - func removeMarker(at suggestions: [W3WSuggestion]?) { - - } - - func removeMarker(at words: [String]?) { - - } - - func removeMarker(at square: W3WSquare?) { - // removeMarker(at: square) - } - - func removeMarker(group: String) { - - } - - func unselect() { - - } - - func hover(at: CLLocationCoordinate2D) { - - } - - func unhover() { - - } - - func set(zoomInPointsPerSquare: CGFloat) { - - } - - func getAllMarkers() -> [W3WSquare] { - return [W3WSquare]() - } - - func removeAllMarkers() { - self.markers.removeAll() - - if let gridData = self.mapGridData { - gridData.squares.removeAll() - gridData.markers.removeAll() - // gridData.selectedSquare = nil - // gridData.squareIsMarker = nil - // gridData.currentSquare = nil - gridData.overlayColors = [:] - gridData.previousStateHash = nil - - for annotation in annotations { - removeAnnotation(annotation) - } - } - } - - func redraw(){ - redrawGrid() - redrawSquares() - redrawPins() - } - - func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { - return nil - } + + func select(at coordinates: CLLocationCoordinate2D, completion: @escaping (Result) -> Void) { + + self.convertTo3wa(coordinates: coordinates, language: self.language) { [weak self] square, error in + + guard let self = self else { return } + + if let e = error { + W3WThread.runOnMain { + self.mapGridData?.onError(e) + completion(.failure(e)) + } + } + if let s = square { + W3WThread.runOnMain { + // self.select(at: s) + completion(.success(s)) + } + } else { + W3WThread.runOnMain { + let e = W3WError.message("No Square Found") + completion(.failure(e)) + } + } + } + } + + func select(at: W3WSquare) { + createMarkerForConditions(at) + } + + func createMarkerForConditions(_ at: W3WSquare) { + + let squares = self.mapGridData?.squares + + let selectedSquare = self.mapGridData?.selectedSquare + + let isMarkerinList = squares?.contains(where: { $0.bounds?.id == at.bounds?.id }) + + let isPrevMarkerinList = squares?.contains(where: { $0.bounds?.id == selectedSquare?.bounds?.id }) + + let annotation = findAnnotation(selectedSquare) + + let markers = self.mapGridData?.markers + + let squareSize = getPointsPerSquare() + + if let selectedSquare = selectedSquare { + if squareSize < self.mapGridData?.pointsPerSquare ?? CGFloat(12.0) { + if (annotation?.isMarker == true && annotation?.isMark == false ) { //check the previous annotation is square + let previousBoxId = selectedSquare.bounds?.id + + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: previousColor) + } + else{ + if markers != nil { + removeSelectedSquare(at: selectedSquare) + addMarkerAsCircle(at: selectedSquare, color: annotation?.color) + } + } + } + else{ + removeSelectedSquare(at: selectedSquare) + } + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) + self.mapGridData?.selectedSquare = at + + return + } + removeSelectedSquare(at: selectedSquare) + } + + //squares + if isMarkerinList == true { + removeSelectedSquare(at: selectedSquare) + + let currentBoxId = at.bounds?.id + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarker(at: selectedSquare, color: previousColor, type: .circle) + } + } + if let color = self.mapGridData?.overlayColors[currentBoxId ?? 0] { + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: false) + } + self.mapGridData?.squareIsMarker = at + + } else { + let previousBoxId = selectedSquare?.bounds?.id + if isPrevMarkerinList == true { + if let previousColor = self.mapGridData?.overlayColors[previousBoxId ?? 0] { + addMarkerAsCircle(at: selectedSquare, color: previousColor) + } + } + + let color: W3WColor = self.mapGridData?.scheme?.colors?.border ?? .black + addSelectedMarker(at: at, color: color, type: .square, isMarker: true, isMark: true) + } + + self.mapGridData?.selectedSquare = at + } + + /// put a what3words annotation on the map showing the address + func addMarker(at square: W3WSquare?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: square, color: color, type: type, completion: completion) + } + + func addMarker(at suggestion: W3WSuggestion?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestion, color: color, type: type, completion: completion) + } + + func addMarker(at word: String?, color: W3WSwiftThemes.W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + + addMarker(at: word, color: color, type: type, completion: completion) + } + + func addMarker(at words: [String]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: words, color: color, type: type, completion: completion) + } + + func addMarker(at coordinate: CLLocationCoordinate2D?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinate, color: color, type: type, completion: completion) + } + + func addMarker(at squares: [W3WSquare]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: squares, color: color, type: type, completion: completion) + } + + func addMarker(at suggestions: [W3WSuggestion]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: suggestions, color: color, type: type, completion: completion) + } + + func addMarker(at coordinates: [CLLocationCoordinate2D]?, color: W3WColor?, type: W3WMarkerType, completion: @escaping MarkerCompletion) { + addMarker(at: coordinates, color: color, type: type, completion: completion) + } + + func removeMarker(at suggestion: W3WSuggestion?) { + + } + + func removeMarker(at words: String?) { + + } + + func removeMarker(at squares: [W3WSquare]?) { + + } + + func removeMarker(at suggestions: [W3WSuggestion]?) { + + } + + func removeMarker(at words: [String]?) { + + } + + func removeMarker(at square: W3WSquare?) { + // removeMarker(at: square) + } + + func removeMarker(group: String) { + + } + + func unselect() { + + } + + func hover(at: CLLocationCoordinate2D) { + + } + + func unhover() { + + } + + func set(zoomInPointsPerSquare: CGFloat) { + + } + + func getAllMarkers() -> [W3WSquare] { + return [W3WSquare]() + } + + func removeAllMarkers() { + self.markers.removeAll() + + if let gridData = self.mapGridData { + gridData.squares.removeAll() + gridData.markers.removeAll() + // gridData.selectedSquare = nil + // gridData.squareIsMarker = nil + // gridData.currentSquare = nil + gridData.overlayColors = [:] + gridData.previousStateHash = nil + + for annotation in annotations { + removeAnnotation(annotation) + } + } + } + + func redraw(){ + redrawGrid() + redrawSquares() + redrawPins() + } + + func findMarker(by coordinates: CLLocationCoordinate2D) -> W3WSquare? { + return nil + } } extension W3WAppleMapHelper { - - public func updateCamera(camera: W3WMapCamera?) { - - W3WThread.runOnMain { [weak self] in - if let self = self { - if let center = camera?.center, let scale = camera?.scale { - let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) - mapView?.setRegion(region, animated: true) - - } else if let center = camera?.center { - mapView?.setCenter(center, animated: true) - - } else if let scale = camera?.scale { - let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) - mapView?.setRegion(region, animated: true) - } - } - } - } - - public func updateSquare(square: W3WSquare?) { - if let square = square { - self.mapGridData?.currentSquare = square - self.select(at: square) - } - } - - private func getNewMarkers(markersLists: W3WMarkersLists) -> W3WMarkersLists { - guard let gridData = self.mapGridData else { - return W3WMarkersLists() - } - - // Create a new markers list to return - let newMarkersLists = W3WMarkersLists() - - // Clear the automatically created default list - newMarkersLists.lists.removeAll() - - // Process each list in the input - for (listName, list) in markersLists.getLists() { - // Skip empty lists or default list with no color - if list.markers.isEmpty || (listName == "default" && list.color == nil) { - continue - } - - // Create a new list for this color - let newList = W3WMarkerList() - newList.color = list.color - newList.type = list.type - - // Track already processed squares for this specific list - var listProcessedIds = Set() - - for marker in list.markers { - if let bounds = marker.bounds { - let squareId = bounds.id - - // Skip if we've already processed this ID in this list - if listProcessedIds.contains(squareId) { - continue - } - - // Check if this square exists in overlay colors - if let existingColor = gridData.overlayColors[squareId] { - // Only include if the color is different - if let listColor = list.color, !colorComponentsMatch(existingColor.cgColor, listColor.cgColor) { - newList.markers.append(marker) - } - } else { - // Square doesn't exist in overlay colors, so include it - newList.markers.append(marker) - } - - // Mark this ID as processed for this list - listProcessedIds.insert(squareId) - } else { - // No bounds, include it - newList.markers.append(marker) - } - } - - // Only add lists with markers - if !newList.markers.isEmpty { - newMarkersLists.add(listName: listName, list: newList) - } - } - - return newMarkersLists - } - - // Helper function to compare color components - private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { - // Check if color spaces match - guard color1.colorSpace?.model == color2.colorSpace?.model else { - return false - } - - // Get components - let components1 = color1.components ?? [] - let components2 = color2.components ?? [] - - // Check if component counts match - guard components1.count == components2.count else { - return false - } - - // Compare components with a small tolerance - let tolerance: CGFloat = 0.001 - for i in 0.. tolerance { - return false - } - } - - return true - } - - public func updateMarkers(markersLists: W3WMarkersLists) { - removeAllMarkers() - select(at: mapGridData?.selectedSquare ?? W3WBaseSquare()) - - let defaultColor = W3WColor(light: .darkBlue, dark: .white) - let defaultType: W3WMarkerType = .circle - - let lists = markersLists.getLists() - - for list in lists.values { - let color = list.color ?? defaultColor - let type = list.type ?? defaultType - - for marker in list.markers { - addMarker(at: marker, color: color, type: type) - } - } - } - - public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { - - self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in - guard self != nil else { return } - - if let error = error { - W3WThread.runOnMain { - completion(nil, error) - } - } else if let s = square { - W3WThread.runOnMain { - completion(s, nil) - } - } - } - } + + public func updateCamera(camera: W3WMapCamera?) { + + W3WThread.runOnMain { [weak self] in + if let self = self { + if let center = camera?.center, let scale = camera?.scale { + let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) + mapView?.setRegion(region, animated: true) + + } else if let center = camera?.center { + mapView?.setCenter(center, animated: true) + + } else if let scale = camera?.scale { + let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) + mapView?.setRegion(region, animated: true) + } + } + } + } + + public func updateSquare(square: W3WSquare?) { + if let square = square { + self.mapGridData?.currentSquare = square + self.select(at: square) + } + } + + private func getNewMarkers(markersLists: W3WMarkersLists) -> W3WMarkersLists { + guard let gridData = self.mapGridData else { + return W3WMarkersLists() + } + + // Create a new markers list to return + let newMarkersLists = W3WMarkersLists() + + // Clear the automatically created default list + newMarkersLists.lists.removeAll() + + // Process each list in the input + for (listName, list) in markersLists.getLists() { + // Skip empty lists or default list with no color + if list.markers.isEmpty || (listName == "default" && list.color == nil) { + continue + } + + // Create a new list for this color + let newList = W3WMarkerList() + newList.color = list.color + newList.type = list.type + + // Track already processed squares for this specific list + var listProcessedIds = Set() + + for marker in list.markers { + if let bounds = marker.bounds { + let squareId = bounds.id + + // Skip if we've already processed this ID in this list + if listProcessedIds.contains(squareId) { + continue + } + + // Check if this square exists in overlay colors + if let existingColor = gridData.overlayColors[squareId] { + // Only include if the color is different + if let listColor = list.color, !colorComponentsMatch(existingColor.cgColor, listColor.cgColor) { + newList.markers.append(marker) + } + } else { + // Square doesn't exist in overlay colors, so include it + newList.markers.append(marker) + } + + // Mark this ID as processed for this list + listProcessedIds.insert(squareId) + } else { + // No bounds, include it + newList.markers.append(marker) + } + } + + // Only add lists with markers + if !newList.markers.isEmpty { + newMarkersLists.add(listName: listName, list: newList) + } + } + + return newMarkersLists + } + + // Helper function to compare color components + private func colorComponentsMatch(_ color1: CGColor, _ color2: CGColor) -> Bool { + // Check if color spaces match + guard color1.colorSpace?.model == color2.colorSpace?.model else { + return false + } + + // Get components + let components1 = color1.components ?? [] + let components2 = color2.components ?? [] + + // Check if component counts match + guard components1.count == components2.count else { + return false + } + + // Compare components with a small tolerance + let tolerance: CGFloat = 0.001 + for i in 0.. tolerance { + return false + } + } + + return true + } + + public func updateMarkers(markersLists: W3WMarkersLists) { + removeAllMarkers() + select(at: mapGridData?.selectedSquare ?? W3WBaseSquare()) + + let defaultColor = W3WColor(light: .darkBlue, dark: .white) + let defaultType: W3WMarkerType = .circle + + let lists = markersLists.getLists() + + for list in lists.values { + let color = list.color ?? defaultColor + let type = list.type ?? defaultType + + for marker in list.markers { + addMarker(at: marker, color: color, type: type) + } + } + } + + public func convertTo3wa(coordinates: CLLocationCoordinate2D, language: W3WLanguage = W3WBaseLanguage.english, completion: @escaping W3WSquareResponse ) { + + self.w3w.convertTo3wa(coordinates: coordinates, language: language) { [weak self] square, error in + guard self != nil else { return } + + if let error = error { + W3WThread.runOnMain { + completion(nil, error) + } + } else if let s = square { + W3WThread.runOnMain { + completion(s, nil) + } + } + } + } } extension W3WAppleMapHelper { - - public func setRegion(_ region: MKCoordinateRegion, animated: Bool) { - mapView?.setRegion(region, animated: false) - } - - public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { - mapView?.setCenter(coordinate, animated: animated) - } + + public func setRegion(_ region: MKCoordinateRegion, animated: Bool) { + mapView?.setRegion(region, animated: false) + } + + public func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) { + mapView?.setCenter(coordinate, animated: animated) + } } From 743fbb90a18962e8192eb411c354ec071b77024e Mon Sep 17 00:00:00 2001 From: aunguyen-w3w Date: Tue, 22 Jul 2025 15:06:08 +0700 Subject: [PATCH 18/20] MT-fix crash in ocr open map flow (#2) * MT-Fix crash in OCR when open the map from result list * Revise the logic more simpler --- .../Helper/W3WAppleMapHelper.swift | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift index c5ef676..d024015 100644 --- a/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift +++ b/Sources/W3WSwiftComponentsMapApple/Helper/W3WAppleMapHelper.swift @@ -471,20 +471,31 @@ public extension W3WAppleMapHelper { extension W3WAppleMapHelper { public func updateCamera(camera: W3WMapCamera?) { - W3WThread.runOnMain { [weak self] in - if let self = self { - if let center = camera?.center, let scale = camera?.scale { - let region = MKCoordinateRegion(center: center, span: scale.asSpan(mapSize: mapView!.frame.size , latitude: center.latitude )) - mapView?.setRegion(region, animated: true) - - } else if let center = camera?.center { - mapView?.setCenter(center, animated: true) - - } else if let scale = camera?.scale { - let region = MKCoordinateRegion(center: mapView!.centerCoordinate, span: scale.asSpan(mapSize: mapView!.frame.size, latitude: camera?.center?.latitude ?? 0.0)) - mapView?.setRegion(region, animated: true) - } + guard let self, + let mapView else { return } + + let center = camera?.center ?? mapView.centerCoordinate + let scale = camera?.scale + let span = scale?.asSpan(mapSize: mapView.frame.size, + latitude: center.latitude) + + // Check center validity + guard CLLocationCoordinate2DIsValid(center) else { + return + } + + // Center and span are available -> make a region + if let span, span.latitudeDelta.isFinite, span.longitudeDelta.isFinite { + let region = MKCoordinateRegion(center: center, span: span) + mapView.setRegion(region, animated: true) + + // No span -> no region -> move to center as a fallback option + } else if camera?.center != nil { + mapView.setCenter(center, animated: true) + + } else { + // Do nothing } } } From 008e4948d80846a733042025ae5d89a9fbee6ec7 Mon Sep 17 00:00:00 2001 From: "khai.do.appetiser" Date: Tue, 4 Nov 2025 15:06:14 +0700 Subject: [PATCH 19/20] update packages --- Package.resolved | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Package.resolved b/Package.resolved index 92e4ccf..63828b5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "07879ecb841c90ffbeaeab1d6491eb5a39d5140d1aa8d8e8a55d4a5347473e87", + "originHash" : "99eb401473e15b93dc9abc60c04c89f797f7bca2dc6d88f506876eec1cad6f4e", "pins" : [ { "identity" : "w3w-swift-app-events", "kind" : "remoteSourceControl", "location" : "git@github.com:w3w-internal/w3w-swift-app-events.git", "state" : { - "revision" : "a3aa579afbd1d3b66574cff785a33edbec1c0452", - "version" : "1.1.0" + "revision" : "f9eb5e0ef166e6d056910b37e6516754a657470e", + "version" : "5.2.0" } }, { @@ -16,7 +16,7 @@ "location" : "git@github.com:what3words/w3w-swift-components-map.git", "state" : { "branch" : "staging", - "revision" : "9823e608625044c42776a11b745c0ffc7b018021" + "revision" : "e803019825cf09fdc9f545213cb25fea3bf90ab4" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/what3words/w3w-swift-core.git", "state" : { "branch" : "staging", - "revision" : "b00c40eeb249645138e705a3def58eee20f73fb0" + "revision" : "2bf417d43d6721fbd8b367d4db13cddd5f1fe9ef" } }, { @@ -34,7 +34,7 @@ "location" : "https://github.com/what3words/w3w-swift-design.git", "state" : { "branch" : "staging", - "revision" : "d9087dd0079917d3cf52c96208b15bbbfc306748" + "revision" : "17c18b2152241fa58d8ace51bb0d66de13d2c6bd" } }, { @@ -43,7 +43,7 @@ "location" : "https://github.com/what3words/w3w-swift-themes.git", "state" : { "branch" : "staging", - "revision" : "991792aa333e57482650216990272152faaeb820" + "revision" : "95405df4bd0b59ee74b4a8813fe3f5385a08ff3d" } } ], From 080993f118ef554d8ff03b57df2a66dbd2e044a7 Mon Sep 17 00:00:00 2001 From: khaidow3w Date: Tue, 4 Nov 2025 20:46:32 +0700 Subject: [PATCH 20/20] Update package version --- Package.resolved | 18 +++++++++--------- Package.swift | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Package.resolved b/Package.resolved index 63828b5..f9a968c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "99eb401473e15b93dc9abc60c04c89f797f7bca2dc6d88f506876eec1cad6f4e", + "originHash" : "99c32a712676667ed5ab12dc5bf2960f71e70611de03347238eda66e64f361ef", "pins" : [ { "identity" : "w3w-swift-app-events", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "git@github.com:what3words/w3w-swift-components-map.git", "state" : { - "branch" : "staging", - "revision" : "e803019825cf09fdc9f545213cb25fea3bf90ab4" + "revision" : "c26ebe3059f503c0545c7ad62b1573c79b61e8ce", + "version" : "1.0.0" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-core.git", "state" : { - "branch" : "staging", - "revision" : "2bf417d43d6721fbd8b367d4db13cddd5f1fe9ef" + "revision" : "7fe8769bfbe105bcf3b05f42aeb8833673780ec1", + "version" : "1.1.4" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-design.git", "state" : { - "branch" : "staging", - "revision" : "17c18b2152241fa58d8ace51bb0d66de13d2c6bd" + "revision" : "2495f4f80c1a872264211ac937f71d15d8516490", + "version" : "1.1.0" } }, { @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/what3words/w3w-swift-themes.git", "state" : { - "branch" : "staging", - "revision" : "95405df4bd0b59ee74b4a8813fe3f5385a08ff3d" + "revision" : "c6249273234f554a452e69ee06d2825d60c7c838", + "version" : "1.4.0" } } ], diff --git a/Package.swift b/Package.swift index f2fa706..0b693ef 100644 --- a/Package.swift +++ b/Package.swift @@ -13,10 +13,10 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/what3words/w3w-swift-themes.git", branch: "staging"), - .package(url: "https://github.com/what3words/w3w-swift-design.git", branch: "staging"), - .package(url: "git@github.com:what3words/w3w-swift-components-map.git", branch: "staging"), - .package(url: "https://github.com/what3words/w3w-swift-core.git", branch: "staging") + .package(url: "https://github.com/what3words/w3w-swift-themes.git", "1.0.0"..<"2.0.0"), + .package(url: "https://github.com/what3words/w3w-swift-design.git", "1.0.0"..<"2.0.0"), + .package(url: "git@github.com:what3words/w3w-swift-components-map.git", "1.0.0"..<"2.0.0"), + .package(url: "https://github.com/what3words/w3w-swift-core.git", "1.0.0"..<"2.0.0") ], targets: [