how can I create objects of my class in swift based on json fetched by swiftyJson? - ios

I have a class as follows:
import Foundation
import MapKit
class SingleRequest: NSObject, MKAnnotation {
var title: String?
let created: String
let discipline: String
let coordinate: CLLocationCoordinate2D
var items = NSMutableArray()
init(title: String, created: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.created = created
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
}
I also have a json that looks like:
[{"_id":"56c9d44fbcb42e075f7d49b1",
"username":"Ms. Brynlee Quitzon DDS",
"photo":"photo.jpg",
"number":"one",
"description":"Maiores rerum beatae molestiae autem. Voluptatem magni aspernatur est voluptas.",
"__v":0,
"updated_at":"2016-02-21T15:14:23.123Z",
"created_at":"2016-02-21T15:14:23.116Z",
"location":{
"type":"Point",
"coordinates":[5.300567929507009,44.04127433959841]}
},
etc.
and now I want to fetch all json entries and create SingleRequest object for each of them.
So far I created a method in this class:
class func getAllRequests() {
print("getAllRequests")
RestApiManager.sharedInstance.getRequests { json in
let results = json//["username"]
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
var title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
let latitude = (user[""]).doubleValue
let longitude = (user[""]).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate)
}
}
}
And now 2 questions:
1) as you can see above, I left those two fields empty:
let latitude = (user[""]).doubleValue
let longitude = (user[""]).doubleValue
that's because I don't know how to refer to the long/lat values from my json, since they are embeded in the coordinates field...
How can I fill it?
2) will this function create needed objects? or should I for example change the declaration to mark some return value:
class func getAllRequests()
? Thanks!

For your first question, you need to first get the array out of user["coordinates"] and then downcast it to Array, user["coordinates"] as? Array<Double>
For your second question, it should return an array of SingleRequest, Array<SingleRequest>
class func getAllRequests() -> Array<SingleRequest> {
var requests: Array<SingleRequest> = []
RestApiManager.sharedInstance.getRequests { json in
let results = json//["username"]
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
var title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
guard let coordinates = user["coordinates"] as? Array<Double> else { print("no lat/long") }
let latitude = coordinates[0]
let longitude = coordinates[1]
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
requests.append(SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate))
}
}
return requests
}

var singleRequestsArray = [SingleRequest]()
class func getAllRequests() {
RestApiManager.sharedInstance.getRequests { json in
for (index: String, subJson: JSON) in son {
let user: AnyObject = subjson.object
let title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
var coordinate = CLLocationCoordinate2D()
if let coordinates = user["coordinates"].array{
let latitude = coordinates.first?).doubleValue
let longitude = coordinates.array.last?).doubleValue
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
singleRequestsArray.append(SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate))
}
}
}
Hope this will help you!

Related

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

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)

Place multiple markers on MKMapView from Firebase Database

I'm creating an iOS app that uses Apple Maps to display markers for Garage/Yard Sales in the local area. So far I've been able to figure out how to place one marker on Apple Maps from the Firebase Database, but I'm not sure how to do it with multiple markers. I've done similar tasks using cells to display different content in a UITableView from Firebase Database but this is my first time doing it map-wise. I was reading an article here that showed how it was possible with JSON data, but due to the fact the marker information will be live, it wouldn't be possible that way for my app. What would be the best way to add multiple markers to a MKMapView?
Snippet from ViewController (Only setup for one marker)
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let artwork = Artwork(title: dictionary["title"] as! String,
locationName: dictionary["location"] as! String,
discipline: dictionary["category"] as! String,
coordinate: CLLocationCoordinate2D(latitude: dictionary["lat"] as! Double, longitude: dictionary["long"] as! Double))
self.mapView.addAnnotation(artwork)
}
})
Artwork.swift
class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
var subtitle: String? {
return locationName
}
}
I was able to figure out a way to this thanks to the link #kosuke-ogawa posted.
Snippet within ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
let ref = Database.database().reference()
let postsRef = ref.child("posts")
postsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
let postSnap = snap as! DataSnapshot
if let dict = postSnap.value as? [String:AnyObject] {
let title = dict["title"] as! String
let locationName = dict["location"] as! String
let discipline = dict["category"] as! String
let coordinate = CLLocationCoordinate2D(latitude: dict["lat"] as! Double, longitude: dict["long"] as! Double)
let artwork = Artwork(title: title, locationName: locationName, discipline: discipline, coordinate: coordinate)
self.mapView.addAnnotation(artwork)
}
}
})
}
If there is a cleaner way to do this feel free to edit my code to help others in the future.
for multiple place marker on MKMapView you can take array of ArtWork That you Have created, please try this bellow solution
var arrArtworks: [Artwork] = []
override func viewDidLoad() {
super.viewDidLoad()
intializeData()
PlaceMarker()
}
func intializeData(){
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let artwork = Artwork(title: dictionary["title"] as! String,
locationName: dictionary["location"] as! String,
discipline: dictionary["category"] as! String,
coordinate: CLLocationCoordinate2D(latitude: dictionary["lat"] as! Double, longitude: dictionary["long"] as! Double))
arrArtworks.append(Artwork)
}
})
}
func PlaceMarker() {
mapView.addAnnotations(arrArtworks)
}
For example, you can use for loop:
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let dictionary = child.value as? [String: AnyObject] {
let artwork = Artwork(title: dictionary["title"] as! String,
locationName: dictionary["location"] as! String,
discipline: dictionary["category"] as! String,
coordinate: CLLocationCoordinate2D(latitude: dictionary["lat"] as! Double, longitude: dictionary["long"] as! Double))
self.mapView.addAnnotation(artwork)
}
}
})
cf. http://takeip.com/swift-3-firebase-to-mapkit.html

Swift Writing to and Accessing Class Objects

So I am trying to use MapKit to put a series of points into a map. Ultimitely I am going to get a list of datapoints from csv and load them into the Location Class, then read them from the Class as MapView Annotations. This works here individually for plotting 2 points manually ...but I can't figure out how to properly load and access Location class for a number of items (10 Location Points for example)
Here is my class file
import Foundation
import MapKit
class Location: NSObject, MKAnnotation{
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, lat: String, lon: String){
self.title = title
self.locationName = locationName
let latDouble = (lat as NSString).doubleValue
let lonDouble = (lon as NSString).doubleValue
let latlong = CLLocationCoordinate2D(latitude: latDouble, longitude: lonDouble)
self.discipline = locationName
self.coordinate = latlong
super.init()
}
}
here is ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
//var stops = TransitStop[]()
let initialLocation = CLLocation( latitude: 41.880632, longitude: -87.623277)
#IBOutlet weak var mapView: MKMapView!
let regionRadius: CLLocationDistance = 1000
override func viewDidLoad() {
super.viewDidLoad()
let initial = Location(title: "YOU", locationName: "are here", lat: "41.880632", lon: "-87.623277")
let firstStop = Location(title: "ashland", locationName: "ashland", lat: "41.88574", lon: "-87.627835")// Do any additional setup after loading the view, typically from a nib.
centerMapOnLocation(location: initialLocation)
mapView.addAnnotation(initial)
mapView.addAnnotation(firstStop)
}
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
}
Try something like this:
var locationArray: [Location] = []
for line in csvString{
let values:[String] = line.components(separatedBy: ",")
let title = values[0]
let locationName = values[1]
let lat = values[2]
let lng = values[3]
let latD: Double = Double(lat)
let lngD: Double = Double(lng)
let location = Location(title: title, locationName: locationName, lat: latD, lng: lngD)
locationArray.append(location)
}
centerMapOnLocation(location: initialLocation)
addLocationsToMap(locationArray: locationArray)
Make sure you do some validating on the values (Double) before implementing.
Then you can read the location into the map with this function:
func addLocationsToMap(locationArray: [Location]){
for location in locationArray {
mapView.addAnnotation(location)
}
}
Note: I realize the OP doesn't need to explicitly define the variables. I just thought it would be easier to understand.
I assume the csv value can be read from an array perhaps and that the placement of the values in the csv file for 'title', 'loacationName' are well defined.

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.

Mapview - protection against coordinates found nil

I have mapview that downloads records off CloudKit. The coordinates of each record is based on forward geocoder, where users add the address (ex: New York, NY) and lats and lons are obtained
Current Model is as follow:
class Place: NSObject
{
var name: String
var address: String
var comment: String?
var photo: UIImage?
var rating: Int
var location: CLLocation?
var identifier: String
var record: CKRecord!
init(record: CKRecord)
{
self.record = record
self.name = record.valueForKey(placeName) as! String
self.address = record.valueForKey(placeAddress) as! String
self.comment = record.valueForKey(placeComment) as? String
if let photoAsset = record.valueForKey(placePhoto) as? CKAsset
{
self.photo = UIImage(data: NSData(contentsOfURL: photoAsset.fileURL)!)
}
self.rating = record.valueForKey(placeRating) as! Int
self.location = record.valueForKey(placeLocation) as? CLLocation
self.identifier = record.recordID.recordName
}
// MARK: Map Annotation
var coordinate: CLLocationCoordinate2D {
get {
return location!.coordinate
}
}
This is my method to place each pin on the mapview.
func placePins()
{
for place: Place in self.places
{
let location = CLLocationCoordinate2DMake(place.coordinate.latitude, place.coordinate.longitude)
let dropPin = CustomPointAnnotation(place: place)
dropPin.pinCustomImageName = "customPin"
dropPin.coordinate = location
dropPin.title = place.title
dropPin.subtitle = place.subtitle
dropPin.name = place.name
dropPin.image = place.photo
mapView.addAnnotation(dropPin)
}
}
How do i fix them to protect against any record that doesn't have coordinates since forward geocoder is not the most reliable way?
What about
for place: Place in self.places
{
if (place.location == nil) {
continue;
}
...
}
Not sure what is the issue there

Resources