Xcode and Swift: Colors and Object ID not being applied to map markers? - ios

I'm making a test app that is taking in geological points of interest from a JSON file and plotting the points on a map (you can see the info I am getting here: https://maine.hub.arcgis.com/datasets/ff3e487fb782464684f8c1f8a1b7e58d_0/about and you can see the JSON file there as well). I've been trying to color-code the points to correspond to the category of geological feature (bedrock is red, coastal is green, surficial is purple, etc.). However, when I try to apply the colors, it doesn't work. The app runs just fine, and I can see all the points, they're just not color-coded or labeled as their object ID.
This is the file I used for each item (titled Artwork because this project was previously locations of artworks in Oahu, but changed to geological formations in Maine):
import Foundation
import MapKit
import Contacts
import SwiftUI
class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String?
let type: String?
let coordinate: CLLocationCoordinate2D
let objectID: Int?
init(title: String?, locationName: String?, type: String?, coordinate: CLLocationCoordinate2D, objectID: Int?) {
self.title = title
self.locationName = locationName
self.type = type
self.coordinate = coordinate
self.objectID = objectID
super.init()
}
init?(feature: MKGeoJSONFeature) {
guard
let point = feature.geometry.first as? MKPointAnnotation,
let propertiesData = feature.properties,
let json = try? JSONSerialization.jsonObject(with: propertiesData),
let properties = json as? [String: Any]
else {
return nil
}
title = properties["SITE_NAME"] as? String
locationName = properties["TOWN"] as? String
type = properties["CATEGORY"] as? String
coordinate = point.coordinate
objectID = properties["OBJECTID"] as? Int
super.init()
}
var subtitle: String? {
return locationName
}
var mapItem: MKMapItem? {
guard let location = locationName else {
return nil
}
let addressDict = [CNPostalAddressStreetKey: location]
let placemark = MKPlacemark(coordinate: coordinate, addressDictionary: addressDict)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = title
return mapItem
}
//This is where I code the method for choosing the color
var markerTintColor: UIColor {
switch type {
case "Bedrock":
return .red
case "Coastal":
return .green
case "Surficial":
return .purple
case "Bedrock, Surficial":
return .blue
case "Bedrock, Surficial, Coastal":
return .cyan
case "Surficial, Coastal":
return .magenta
case "Bedrock, Coastal":
return .orange
default:
return .gray
}
}
}
Here is another file that might be important, ArtworkViews.swift:
import Foundation
import MapKit
import SwiftUI
class ArtworkViews: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let artwork = newValue as? Artwork else {
return
}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
//This is where the color and ID are applied
markerTintColor = artwork.markerTintColor
if let ID = artwork.objectID {
glyphText = String(ID)
}
}
}
}
And here is where I implement the colors in ViewController.swift (this the viewDidLoad() function):
let initialLocation = CLLocation(latitude: 44.883427, longitude: -68.670815)
mapView.centerToLocation(initialLocation)
let maineCenter = CLLocation(latitude: 44.883427, longitude: -68.670815)
let region = MKCoordinateRegion(center: maineCenter.coordinate, latitudinalMeters: 600000, longitudinalMeters: 300000)
mapView.setCameraBoundary(MKMapView.CameraBoundary(coordinateRegion: region), animated: true)
let zoomRange = MKMapView.CameraZoomRange(maxCenterCoordinateDistance: 1400000)
mapView.setCameraZoomRange(zoomRange, animated: true)
mapView.delegate = self
//This is where the color is ultimately applied
mapView.register(ArtworkViews.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
loadInitialData()
mapView.addAnnotations(artworks)

Related

UIlabel() tap to expand text

So I am building a map that provides factual data on a variety of points located in Washington DC. The data is getting pulled from a geojson and listed as 20 or so points. When you click on the point, there is a popup that occurs that shows a truncated version of the text, only 10 lines. However some of the text is really long, 200 lines+. I want to develop a way to expand the text, or have a scrollable bar. Basically anything to have the option to open up the text. Adding an image to show what it looks like.
You can see an image here: https://i.stack.imgur.com/yeeBa.jpg
Here is the code I am using.
The data is getting called in the ViewController.swift using this
func loadInitialData() {
// 1
guard let fileName = Bundle.main.path(forResource: "PublicArt4", ofType: "json")
else { return }
let optionalData = try? Data(contentsOf: URL(fileURLWithPath: fileName))
guard
let data = optionalData,
// 2
let json = try? JSONSerialization.jsonObject(with: data),
// 3
let dictionary = json as? [String: Any],
// 4
let works = dictionary["data"] as? [[Any]]
else { return }
// 5
let validWorks = works.compactMap { Artwork(json: $0) }
artworks.append(contentsOf: validWorks)
}
}
The artworkViews.swift displays the label formatting
let detailLabel = UILabel()
detailLabel.text = artwork.locationlink
detailLabel.text = artwork.subtitle
detailCalloutAccessoryView = detailLabel
detailLabel.font = UIFont(name: "Heiti TC", size: 12)
detailLabel.numberOfLines = 10
//detailLabel.adjustsFontSizeToFitWidth = true
// detailLabel.minimumScaleFactor = 0.5
// detailLabel.baselineAdjustment = .alignCenters
// detailLabel.textAlignment = .left
The geojson is formatted like this
{"data" : [ [
1,
"Washington Monument",
"Click here to learn more",
"Madison Dr NW & 15th St NW",
"Washington",
"DC",
20001,
"38.89013",
"-77.033031",
"www.google.com Welcome to the Washington Monument,",
"Mural",
"Some text, not sure what it is",
" "
],...
The Artwork.swift pulls the data in such a way
class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String
let locationURL: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, locationURL: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.locationURL = locationURL
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
var subtitle: String? {
return locationName
}
var locationlink: String? {
return locationURL
}
init?(json: [Any]) {
// 1
if let title = json[2] as? String {
self.title = title
} else {
self.title = "No Title"
}
// json[11] is the long description
//self.locationName = json[11] as! String
// json[12] is the short location string
self.locationName = json[9] as! String
self.discipline = json[10] as! String
self.locationURL = json[3] as! String
// 2
if let latitude = Double(json[7] as! String),
let longitude = Double(json[8] as! String) {
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
self.coordinate = CLLocationCoordinate2D()
}
}
// pinTintColor for disciplines: Sculpture, Plaque, Mural, Monument, other
var markerTintColor: UIColor {
switch discipline {
case "Monument":
return .red
case "Mural":
return .cyan
case "Plaque":
return .blue
case "Sculpture":
return .purple
default:
return .green
}
}
var imageName: String? {
if discipline == "Mural" { return "Flag" }
return "Flag"
}
// Annotation right callout accessory opens this mapItem in Maps app
func mapItem() -> MKMapItem {
let addressDict = [CNPostalAddressStreetKey: subtitle!]
let placemark = MKPlacemark(coordinate: coordinate, addressDictionary: addressDict)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = title
return mapItem
}
}
The one thing I tried was to switch it to a UITextView, but i cant get that to load properly. Mainly because I am not sure how to integrate it into the ViewController.
let detailLabel = UITextView()
detailLabel.text = artwork.locationlink
detailLabel.text = artwork.subtitle
detailLabel.textColor = UIColor.red
detailLabel.selectedTextRange = detailLabel.textRange(from: detailLabel.beginningOfDocument, to: detailLabel.beginningOfDocument)
```

Two Functions are calling in Swift

I am using GoogleMaps to show the location marker on screens after fetching the location from Firestore database but the problem is I have three functions.
First function is showing all the list of users on the google maps, I called it in viewDidLoad() method.
func showListOfAllUsers() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
marker.icon = UIImage(named: "marker")
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
Now I presented a list of users in which I am passing the selected user co-ordinates to show the markers on the GoogleMaps.
func getAllLocationOfSelectedUserFromFirestore() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
I used delegate method to pass the selected user information.
extension MapViewController: ShowTrackingSalesMenListVCDelegate {
func didSelectedFilters(_ sender: ShowTrackingSalesMenListViewController, with userID: String) {
self.selectedUserID = userID
self.userLogButton.isHidden = false
print("The selected UserID is \(selectedUserID)")
self.getAllLocationOfSelectedUserFromFirestore() // called here the second function
}
Here is GMSMapViewDelegate function in which I am passing the user informations in userData.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print("didTap marker")
self.view.endEditing(true)
self.mapView.endEditing(true)
if let _ = self.activeMarker {
self.infoWindowView.removeFromSuperview()
self.activeMarker = nil
}
self.infoWindowView = MarkerInfoView()
let point = mapView.projection.point(for: marker.position)
self.infoWindowView.frame = CGRect(x: (point.x-(self.infoWindowView.width/2.0)), y: (point.y-(self.infoWindowView.height+25.0)), width: self.infoWindowView.width, height: self.infoWindowView.height)
self.activeMarker = marker
for mark in location {
self.infoWindowView.storeNameLabel?.text = mark.name
}
print(self.infoWindowView.storeNameLabel?.text as Any)
if let data = marker.userData as? [String:Any] {
print(data)
self.storeMapData = data
print(self.storeMapData)
var name = "N/A"
if let obj = data["name"] as? String {
name = obj
}
} else {
}
infoWindowView.delegate = self
self.mapView.addSubview(self.infoWindowView)
return true
}
It is showing the marker of the selected user on GoogleMaps. Now the problem is GMSMapViewDelegate function is same for both the above functions and it is showing the markers from both the functions on map. But I want to show only the selected user information on Maps. The red marker showing the selected user locations. How can I do this?
Just put a boolean flag and when you select the user set it to true and check it in the delegate and clear map overlay and put your marker only

Strange behaviour in showing annotations images on map using data coming from Firebase. SWIFT 4.1

The strange behaviour is that when I add a new annotation, either tapped or user location, it gets displayed with the right chosen icon. When MapVC load for the first time, the posts retrieved from Firebase have all the same icon, ( the icon name of the latest one posted. If, after posting a new one, I exit mapViewVc to the menuVC and re enter mapViewVC than every icon is displaying the same icon again, now being my previously posted one.
a Few times it happened the the icons were two different icons, randomly chosen.
I don't understand why the coordinates are taken right but the image is not.
The app flow is:
I have a mapView vc where I can either double tap on screen and get coordinate or code user location coordinate via a button and then get to an chooseIconVc where I have all available icons to choose for the annotation. Once I select one, the icon name get passed back in in mapViewVC in unwindHere() that stores icon name into a variable and coordinates into another. In postAlertNotification those variables get posted to Firebase.
In displayAlerts() the data from Firebase gets stored into variables to initialise an annotation and gets added to mapView.
chosen icon:
#IBAction func unwindHere(sender:UIStoryboardSegue) { // data coming back
if let sourceViewController = sender.source as? IconsViewController {
alertNotificationType = sourceViewController.dataPassed
if tapCounter > 0 {
alertNotificationLatitude = String(describing: alertCoordinates.latitude)
alertNotificationLongitude = String(describing: alertCoordinates.longitude)
postAlertNotification() // post new notification to Firebase
} else {
alertCoordinates = self.trackingCoordinates
alertNotificationLatitude = String(describing: self.trackingCoordinates!.latitude)
alertNotificationLongitude = String(describing: self.trackingCoordinates!.longitude)
postAlertNotification() // post new notification to Firebase
}
}
}
than post:
func postAlertNotification() {
// to set next notification id as the position it will have in array ( because first position is 0 ) we use the array.count as value
let latitude = alertNotificationLatitude
let longitude = alertNotificationLongitude
let alertType = alertNotificationType
let post: [String:String] = [//"Date" : date as! String,
//"Time" : time as! String,
"Latitude" : latitude as! String,
"Longitude" : longitude as! String,
"Description" : alertType as! String]
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("Community").child("Alert Notifications").childByAutoId().setValue(post)
}
retrieve and display:
func displayAlerts() {
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
self.alertIconToDisplay = data["Description"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
self.userAlertAnnotation = UserAlert(type: self.alertIconToDisplay!, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})
}
and
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: userAlertAnnotation, reuseIdentifier: "") // CHANGE FOR NEW ANNOTATION : FULL DATA
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: alertIconToDisplay!) // choose the image to load
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
the variables declarations :
var alertIconToDisplay: String?
var userAlertAnnotation: UserAlert!
var alertNotificationType: String?
var alertNotificationLatitude: String?
var alertNotificationLongitude: String?
UPDATE:
annotation cLass:
import MapKit
class UserAlert: NSObject , MKAnnotation {
var type: String?
var firebaseKey: String?
var coordinate = CLLocationCoordinate2D()
var image: UIImage?
override init() {
}
init(type:String, coordinate:CLLocationCoordinate2D, firebaseKey: String) {
self.type = type
self.firebaseKey = firebaseKey
self.coordinate = coordinate
}
}
After understanding where the problem I was explained how to changed the displayAlert() into
func displayAlerts() { // rajish version
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
let id = Int(data["Id"]!)
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
var userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!)
self.userAlertNotificationArray.append(userAlertAnnotation) // array of notifications coming from Firebase
print("user alert array after append from Firebase is : \(self.userAlertNotificationArray)")
self.alertNotificationArray.append(recombinedCoordinate) // array for checkig alerts on route
self.mapView.addAnnotation(userAlertAnnotation)
})
}
and the mapView to:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // rajish version
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
if annotation is MKUserLocation{
return nil
} else {
print(annotation.coordinate)
annotationView.image = UIImage(named:(annotationView.annotation?.title)! ?? "")
// annotationView.canShowCallout = true
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
that solved it.

SWIFT 4.1 Cannot invoke initializer for type 'Double' with an argument list of type '(String?)'

I'm retrieving mapView annotations posted in Firebase to show them on map, but while converting String values for latitude and longitude to recombine them into CLLocationCoordinates2D I get the error. I don't understand why, because in another function I use the same method but getting the values from arrays but I don't get the error. Also on retrieving the data I would like to also use the key value from firebase as initialiser for my annotations. But I get two more errors Use of unresolved identifier 'firebaseKey' and Use of unresolved identifier 'recombinedCoordinate' for initialisers. Here're the function:
func displayAlerts() {
// FIREBASE: Reference
ref = Database.database().reference()
// FIREBASE:Retrieve posts and listen for changes
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
let data = snapshot.value as? [String:String]
if let actualData = data {
let dataLatitude = data!["Latitude"]
let dataLongitude = data!["Longitude"]
self.alertIconToDisplay = data!["Description"]
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
var recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
self.dummyFunctionToFoolFirebaseObservers()
}
let dataKey = snapshot.key as? String
if let firebaseKey = dataKey {
print("Longitude DataKey is \(String(describing: dataKey))")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
self.dummyFunctionToFoolFirebaseObservers()
}
print("fir long \((snapshot.value!, snapshot.key))")
userAlertAnnotation = UserAlert(type: self.alertIconToDisplay, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})
}
Here's the annotation model :
class UserAlert: NSObject , MKAnnotation {
var type: String?
var firebaseKey: String?
var coordinate:CLLocationCoordinate2D
init(type:String, coordinate:CLLocationCoordinate2D, firebaseKey: String) {
self.type = type
self.firebaseKey = firebaseKey
self.coordinate = coordinate
}
}
What am I doing wrong here? I understand that the error on the initialisers are because initialisation occurs in key closures, but how then I incorporate all data into initialiser ?
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: userAlertAnnotation, reuseIdentifier: "") // CHANGE FOR NEW ANNOTATION : FULL DATA
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: alertIconToDisplay!) // choose the image to load
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
func postAlertNotification() {
// to set next notification id as the position it will have in array ( because first position is 0 ) we use the array.count as value
let latitude = alertNotificationLatitude
let longitude = alertNotificationLongitude
let alertType = alertNotificationType
let post: [String:String] = [//"Date" : date as! String,
//"Time" : time as! String,
"Latitude" : latitude as! String,
"Longitude" : longitude as! String,
"Description" : alertType as! String]
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("Community").child("Alert Notifications").childByAutoId().setValue(post)
}
The error in the topic says that you can't create a Double from an optional String which is true.
To solve it force unwrap the values for Latitude and Longitude.
But the main issue is a scope issue, all variables used in the initializer must be in the same scope. You can flatten the scope with guard statements:
...
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
self.alertIconToDisplay = data["Description"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
// self .keyaLon = dataKey
// self.keyaLonArray.append(firebaseKey)
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
self.userAlertAnnotation = UserAlert(type: self.alertIconToDisplay, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})

How can I rewrite this function so that it uses SwiftyJSON instead of JSON.swift?

I'm looking at the Ray Wenderlich tutorial http://www.raywenderlich.com/90971/introduction-mapkit-swift-tutorial and he is using there this function:
class func fromJSON(json: [JSONValue]) -> Artwork? {
// 1
var title: String
if let titleOrNil = json[16].string {
title = titleOrNil
} else {
title = ""
}
let locationName = json[12].string
let discipline = json[15].string
// 2
let latitude = (json[18].string! as NSString).doubleValue
let longitude = (json[19].string! as NSString).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
// 3
return Artwork(title: title, locationName: locationName!, discipline: discipline!, coordinate: coordinate)
}
Since I'm using SwiftyJSON in my project I would like to stay with that, so I thought about rewriting this function based on that.
If I understand correctly, this function takes one json node and creates Artwork object from it.
So how can I refer to a single json node with SwiftyJSON?
I tried doing:
class func fromJSON(JSON_: (data: dataFromNetworking))->Artwork?{
}
but it causes error use of undeclared type dataFromNetworking. On the other hand that's exactly how they use it in the documentation https://github.com/SwiftyJSON/SwiftyJSON
Could you help me with rewriting it?
My suggestion: separate the model layer from the presentation layer.
ArtworkModel
First of all you need a way to represent the data. A struct is perfect for this.
struct ArtworkModel {
let title: String
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init?(json:JSON) {
guard let
locationName = json[12].string,
discipline = json[15].string,
latitudeString = json[18].string,
latitude = Double(latitudeString),
longitueString = json[19].string,
longitude = Double(longitueString) else { return nil }
self.title = json[16].string ?? ""
self.locationName = locationName
self.discipline = discipline
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
As you can see ArtworkModel is capable to initialize itself from a json.
The presentation layer
Now the Artwork (conform to MKAnnotation) becomes much easier.
class Artwork: NSObject, MKAnnotation {
private let artworkModel: ArtworkModel
init(artworkModel: ArtworkModel) {
self.artworkModel = artworkModel
super.init()
}
var title: String? { return artworkModel.title }
var subtitle: String? { return artworkModel.locationName }
var coordinate: CLLocationCoordinate2D { return artworkModel.coordinate }
}
Usage
You function now becomes
class func fromJSON(json: JSON) -> Artwork? {
guard let model = ArtworkModel(json: json) else { return nil }
return Artwork(artworkModel: model)
}
To use SwiftyJSON in this project first you have to change the method to retrieve the data from the property list file.
Note: This replacement is for Swift 2.
Replace the method loadInitialData() in ViewController with
func loadInitialData() {
do {
let fileName = NSBundle.mainBundle().pathForResource("PublicArt", ofType: "json")
let data = try NSData(contentsOfFile: fileName!, options: NSDataReadingOptions())
let jsonObject = JSON(data:data)
if let jsonData = jsonObject["data"].array {
for artworkJSON in jsonData {
if let artworkJSONArray = artworkJSON.array, artwork = Artwork.fromJSON(artworkJSONArray) {
artworks.append(artwork)
}
}
}
} catch let error as NSError {
print(error)
}
}
And then just exchange [JSONValue] in the method
class func fromJSON(json: [JSONValue]) -> Artwork? {
of the Artworkclass with [JSON], so it's now
class func fromJSON(json: [JSON]) -> Artwork? {
That's it.

Resources