How to encode [CLLocation] properly in swift? - swift3

I am doing an application which need to store the user's data in local. I read the documentation in Apple website, it covers part of the info in how to encode basic type, such as String and Int. However, when I try to encode with the [CLLocation]type, it failed. I am asking if some expert can give me any hint on how to encode such type in Swift?
Here is my code about the Model class.
import Foundation
import UIKit
import CoreLocation
import os.log
import CoreData
//route model
//store the model in the local database.
//change all the [CLLocations] to become the String inorder to store it in local.???? not willing
class Route: NSObject, NSCoding {
//MARK: Properties
var name :String
var area : String
var image: UIImage?
var date : DispatchTime
var routePoints = [CLLocation]()
var rating: Int
//MARK: Achieving paths
//static properties, belong to the
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Routes")
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(image, forKey: PropertyKey.image)
aCoder.encode(date, forKey: PropertyKey.date)
aCoder.encode(area, forKey: PropertyKey.area)
//unable to encode the [cllocation]
aCoder.encode(routePoints, forKey: PropertyKey.routePoints)
aCoder.encode(rating, forKey: PropertyKey.rating)
}
//should also added in the locations as one of the variable.
init?(Name: String, Area: String, Date: DispatchTime, Image: UIImage?, RoutePoints: [CLLocation], Rating: Int) {
guard (Rating >= 0) && (Rating <= 5) else {
return nil
}
self.name = Name
self.area = Area
self.image = Image
self.date = Date
self.routePoints = RoutePoints
self.rating = Rating
}
required convenience init?(coder aDecoder: NSCoder){
//this is the necessary property
//these are optional properties
let rating = aDecoder.decodeInteger(forKey: PropertyKey.rating)
guard let date = aDecoder.decodeObject(forKey: PropertyKey.date) as? DispatchTime,
let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String,
let image = aDecoder.decodeObject(forKey: PropertyKey.image) as? UIImage,
let area = aDecoder.decodeObject(forKey: PropertyKey.area) as? String,
let routePoints = aDecoder.decodeObject(forKey: PropertyKey.routePoints) as? [CLLocation] else{
print("Unable to decode")
return nil
}
self.init(Name: name, Area: area, Date: date, Image: image, RoutePoints: routePoints, Rating: rating)
}
//MARK: Types
struct PropertyKey {
static let name = "name"
static let image = "image"
static let date = "date"
static let area = "area"
static let rating = "rating"
static let routePoints = "routePoints"
}
}
Best wishes

The reason is that CLLocation can't be stored with NSCoder. You could implement a simple coder / decoder using a swift map for this type of value to basically store the locations in a dictionary.
let p1 = CLLocation(latitude: 1, longitude: 1)
let p2 = CLLocation(latitude: 2, longitude:2)
var locations = [p1, p2]
let codedArray = locations.map{ ["lat":$0.coordinate.latitude, "long":$0.coordinate.longitude] }
let decodedArray = codedArray.map{ CLLocation(latitude:$0["lat"]!, longitude:$0["long"]!) }

Related

#Published Array is not updating

i'm currently struggling to fetch any changes from an published variable in SwiftUI. Most of the code is created after this tutorial on YouTube.
It's basically an app, that fetches cryptos from a firebase database. To avoid high server costs I want to update any changes of the coins to the database but not have an observer to lower the download rate.
What's the bug?
When I'm adding a coin to my favorites, it sends the data correctly to the database and updates the UI. However when I try to filter the coins the Coin-array switches back to it's previous state. I also added a breakpoint on the CoinCellViewModel(coin: coin)-Line but it only gets executed when I change the filterBy. Here's a little visualisation of the bug:
Repository
class CoinsRepository: ObservableObject {
#Published var coins = [Coin]()
var ref: DatabaseReference!
init() {
self.ref = Database.database().reference()
loadDatabase(ref)
}
func loadDatabase(_ ref: DatabaseReference) {
ref.child("coins").observeSingleEvent(of: .value) { snapshot in
guard let dictionaries = snapshot.value as? [String: Any] else { return }
var coinNames: [String] = []
self.coins = dictionaries.compactMap({ (key: String, value: Any) in
guard let dic = value as? [String: Any] else { return nil }
coinNames.append(dic["name"] as? String ?? "")
return Coin(dic)
})
}
}
func updateFavorite(_ coin: Coin, state: Bool) {
let path = ref.child("coins/\(coin.name)")
var flag = false
path.updateChildValues(["favorite": state]) { err, ref in
if let err = err {
print("ERROR: \(err.localizedDescription)")
} else {
var i = 0
var newCoinArray = self.coins
for coinA in newCoinArray {
if coinA.name == coin.name {
newCoinArray[i].favorite = state
}
i += 1
}
// I guess here's the error
DispatchQueue.main.async {
self.objectWillChange.send()
self.coins = newCoinArray
}
}
}
}
}
ViewModel
class CoinListViewModel: ObservableObject {
#Published var coinRepository = CoinsRepository()
#Published var coinCellViewModels = [CoinCellViewModel]()
#Published var filterBy: [Bool] = UserDefaults.standard.array(forKey: "filter") as? [Bool] ?? [false, false, false]
#Published var fbPrice: Double = 0.00
#Published var searchText: String = ""
private var cancellables = Set<AnyCancellable>()
init() {
$searchText
.combineLatest(coinRepository.$coins, $fbPrice, $filterBy)
.map(filter)
.sink { coins in
self.coinCellViewModels = coins.map { coin in
CoinCellViewModel(coin: coin)
}
}
.store(in: &cancellables)
}
...
}
updateFavorite(_ coin: Coin, state: Bool) get's called in the CoinCellViewModel() but I guess the code isn't necessary here...
I'm fairly new to the Combine topic and not quite getting all the new methods, so any help is appreciated!

Initialization and Storage Error of API Data

I have an exchange rate API initialization / storage problem. I read in some currency exchange rates and would like to store the data temporally in moneyRates then move the data to rateArray as ordered data. I am getting the error "No exact matches in call to initializer". The error is occurring at the line that begins "let result = try JSONSerialization...". I am also seeing a message in the sidebar (Xcode gray !) "/Foundation.Data:29:23: Candidate requires that the types 'MoneyRates' and 'UInt8' be equivalent (requirement specified as 'S.Element' == 'UInt8')". I'm guessing that I need to initialize moneyRates with some kind of format info.
I would like some explanation of the moneyRates error and how to resolve it. I'm not concerned with rateArray at this point. Thanks for your assistance.
struct MoneyRates {
let date: String
let base: String
let rates: [String: Double]
}
class CurrencyRates: ObservableObject{
#Published var moneyRates: [MoneyRates]
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
} else {
rateArray = [Double] (repeating: 0.0, count: 170)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
// retrieve exchange rates for all 150+ countries from internet and save to rateArray
func updateRates(baseCur: String) {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let result = try JSONSerialization.jsonObject(with: Data(moneyRates)) as! [String:Any] // <-- error is occurring here
var keys = Array(arrayLiteral: result.keys)
if let dateIndex = keys.firstIndex(of: "date"),
let date = result[keys[dateIndex]] as? String, keys.count == 2 {
keys.remove(at: dateIndex)
let base = keys.first!
let rates = MoneyRates(date: date, base: base, rates: result[base] as! [String:Double])
print(rates)
}
} catch {
print(error)
}
}
}.resume()
}
}
If you're trying to decode the result that you get from the URLSession, then instead of passing Data(moneyRates) to decode, you should be passing data from the dataTask closure:
let result = try JSONSerialization.jsonObject(with: data) as! [String:Any]

How to draw a Route between CurrentLocation to SearchedLocation in MkMapView in Swift

I need current location as a source and searched location as a destination, but I got the current location but here I am unable to bring coordinates(latitude and longitude) from searched location to destination.
here my destination shows nil why?
Below is the code please help me.
import UIKit
import MapKit
import CoreLocation
class MapSampViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UISearchBarDelegate {
//Privacy - Location When In Use Usage Description, Privacy - Location Always Usage Description-------these two add in info.plist
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var mapView: MKMapView!
var source: CLLocationCoordinate2D!
var destination: CLLocationCoordinate2D!
var myaddress:String!
var mycity:String!
var mystate:String!
var mycountry:String!
var mytitle:String!
var mylongitude:String!
var mylatitude:String!
var locationtoSearch:String!
let locationManager = CLLocationManager()
var currentlocationPlacemark: CLPlacemark!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
mapView.delegate = self
mapView.showsScale = true
mapView.showsPointsOfInterest = true
mapView.showsUserLocation = true
if CLLocationManager.locationServicesEnabled()
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
// self.showDirection()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
locationtoSearch = self.searchBar.text
var geocoder:CLGeocoder = CLGeocoder()
geocoder.geocodeAddressString(locationtoSearch!, completionHandler: {(placemarks, error) -> Void in
if((error) != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0] as? CLPlacemark {
var coordinates:CLLocationCoordinate2D = placemark.location!.coordinate
var pointAnnotation:MKPointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = coordinates
print(coordinates)
// pointAnnotation.title = "\(String(describing: placemark.name)),\(String(describing: placemark.locality)), \(String(describing: placemark.administrativeArea)), \(String(describing: placemark.country))"
self.myaddress = placemark.name
self.mycity = placemark.locality
self.mystate = placemark.administrativeArea
self.mycountry = placemark.country
pointAnnotation.title = "\(self.myaddress),\(self.mycity),\(self.mystate),\(self.mycountry)"
self.mylongitude = String(stringInterpolationSegment: placemark.location?.coordinate.longitude)
self.mylatitude = String(stringInterpolationSegment: placemark.location?.coordinate.latitude)
self.mapView?.addAnnotation(pointAnnotation)
self.mapView?.centerCoordinate = coordinates
print("coordinates \(coordinates)")
print("The latitude \(self.mylatitude)")
print("The longitude \(self.mylongitude)")
self.mapView?.selectAnnotation(pointAnnotation, animated: true)
}
})
self.showDirection()//i called here or in view viewDidLoad
let annotationsToRemove = mapView.annotations.filter { $0 !== self.mapView.userLocation
}
mapView.removeAnnotations( annotationsToRemove )
}
func showDirection()
{
source = locationManager.location?.coordinate//17.6881° N, 83.2131° E
// let destination = CLLocationCoordinate2DMake(24.9511, 121.2358 )//If i give like this its working
destination = CLLocationCoordinate2DMake(Double(mylongitude)!, Double(mylongitude)!)//fatal error: unexpectedly found nil while unwrapping an Optional value
let sourcePlacemark = MKPlacemark(coordinate: source!)
let destinationPlacemark = MKPlacemark(coordinate: destination)
let sourceItem = MKMapItem(placemark: sourcePlacemark)
let destinationItem = MKMapItem(placemark: destinationPlacemark)
let directionReq = MKDirectionsRequest()
directionReq.source = sourceItem
directionReq.destination = destinationItem
directionReq.transportType = .automobile
let directions = MKDirections(request: directionReq)
directions.calculate(completionHandler: {(response, error) in
if error != nil {
print("Error getting directions")
}
else {
let route = response?.routes[0]
self.mapView.add((route?.polyline)!, level:.aboveRoads)
let rekt = route?.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rekt!), animated: true)
}
})
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let rendrer = MKPolylineRenderer(overlay: overlay)
rendrer.strokeColor = UIColor.blue
rendrer.lineWidth = 3
return rendrer
}
}
here i called showDirection() func in searchBarSearchButtonClicked but it is getting called before coming here why?
Direction requests are executed asynchronously. This means that the rest of your app doesn't wait for the direction to be fetched.
Your showDirection function is both fetching the direction and adding it to the mapView. It would be best to separate these functionalities. You can fetch the direction, update a route variable and have an observer on it which will add the route to the map once it has been fetched.
#IBOutlet weak var mapView: MKMapView!
var route: MKRoute? {
didSet {
mapView.add((route?.polyline)!, level:.aboveRoads) }
}

Saving Coordinates Swift 3 Xcode 8

I am having trouble storing coordinates into a variable. I set a variable to type string! and then when the function runs the latitude and longitude are both stored separately. I am also performing a segue because the sign-up process I am using requires multiple screens.
The data then gets stored into firebase. All of my other fields get uploaded to firebase but, the latitude and longitude do not show up at all.
Here is my code.
import UIKit
import CoreLocation
class SignUpLocation: UIViewController {
lazy var geocoder = CLGeocoder()
var latitude: String!
var longitude: String!
//Geocoding
private func processResponse(withPlacemarks placemarks: [CLPlacemark]?, error: Error?) {
if let error = error {
print("Unable to Forward Geocode Address (\(error))")
locationLabel.text = "Unable to Find Location for Address"
} else {
var location: CLLocation?
if let placemarks = placemarks, placemarks.count > 0 {
location = placemarks.first?.location
}
if let location = location {
let coordinate = location.coordinate
locationLabel.text = "\(coordinate.latitude), \(coordinate.longitude)"
latitude = "\(coordinate.latitude)"
longitude = "\(coordinate.longitude)"
} else {
locationLabel.text = "No Matching Location Found"
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let thirdVC = segue.destination as! SignUpContact
//:::: Initial Sign Up Values
thirdVC.firstNameVar2 = firstNameVar
thirdVC.businessNameVar2 = businessNameVar
thirdVC.emailVar2 = emailVar
thirdVC.passwordVar2 = passwordVar
//:::: Second Sign Up Values
thirdVC.streetAddressVar = streetAddressTextField.text!
thirdVC.cityVar = cityTextField.text!
thirdVC.countryVar = countryTextField.text!
thirdVC.stateVar = stateTextField.text!
thirdVC.zipcodeVar = zipcodeTextField.text!
thirdVC.latitudeVar = latitude
thirdVC.longitudeVar = longitude
}

How to initialize class with optional properties in Swift 3

I get a web API response, which I loop through and initialize a Customer class for each JSON object in the array. The Customer class has a base class with a couple optional properties. I get errors when there are null values in the JSON, and I am not sure how to handle them properly. I am especially unsure of how to handle a null Date in swift. If anyone could give me some advice, I would really appreciate it!
JSON returned from Web API:
[{"Id":1,"BusinessId":1,"CompanyName":"Test Company4","FirstName":"Mike","LastName":"Doe","CustomerType":1,"CustomerStatus":1,"IsDeleted":false,"Created":"2016-12-22T20:12:10.2760144Z","CreatedBy":1,"Modified":"2016-12-29T19:29:26.1245219Z","ModifiedBy":1},{"Id":2,"BusinessId":1,"CompanyName":"Test Company5","FirstName":"Mike","LastName":"Doe","CustomerType":1,"CustomerStatus":1,"IsDeleted":false,"Created":"2017-01-03T20:18:51.7639708Z","CreatedBy":1,"Modified":null,"ModifiedBy":null}]
Customer class:
class Customer : BaseEntity {
var Id: Int64
var BusinessId: Int64
var CustomerType: Int64
var CustomerStatus: Int64
var CompanyName: String
var FirstName: String
var LastName: String
init?(json: [String: Any]) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
guard let id = json["Id"] as? Int64,
let businessId = json["BusinessId"] as? Int64,
let customerType = json["CustomerType"] as? Int64,
let customerStatus = json["CustomerStatus"] as? Int64,
let companyName = json["CompanyName"] as? String,
let firstName = json["FirstName"] as? String,
let lastName = json["LastName"] as? String,
let isDeleted = json["IsDeleted"] as? Bool
else {
return nil
}
var created = Date()
if let cdate = json["Created"] as? String {
created = formatter.date(from: cdate)!
}
let createdBy = json["CreatedBy"] as? String
var modified = Date()
if let mdate = json["Modified"] as? String {
modified = formatter.date(from: mdate)!
}
let modifiedBy = json["ModifiedBy"] as? String
self.Id = id
self.BusinessId = businessId
self.CustomerType = customerType
self.CustomerStatus = customerStatus
self.CompanyName = companyName
self.FirstName = firstName
self.LastName = lastName
super.init(isDeleted: isDeleted, created: created, createdBy: createdBy, modified: modified, modifiedBy: modifiedBy)
}
}
BaseEntity class that contains some optional properties:
class BaseEntity {
var IsDeleted: Bool
var Created: Date
var CreatedBy: String
var Modified: Date?
var ModifiedBy: String?
init(isDeleted: Bool, created: Date, createdBy: String, modified: Date, modifiedBy: String) {
self.IsDeleted = isDeleted
self.Created = created
self.CreatedBy = createdBy
self.Modified = modified
self.ModifiedBy = modifiedBy
}
}
One of the problems I am running into is when I initialize Customer, my Customer object looks like this. Then when I try to insert it into a database, it does not recognize the BaseEntity properties as properties of Customer:
Id = 1
BusinessId = 1
CustomerType = 1
CustomerStatus = 1
CompanyName = "Test Company4"
FirstName = "Mike"
LastName = "Doe"
BaseEntity
IsDeleted = true
Created = '1/1/2017'
CreatedBy = 1
Modified = '1/1/2017'
ModifiedBy = 1
You need to define your class type for model
class BaseEntity {
to
class BaseEntity: NSObject {
This is the way you can create your model class: -
//Model Class
class BaseModel: NSObject {
var name: String
var address: String
var mobilenumber: Int
init(name: String?, address: String?, mobilenumber: Int?) {
self.name = name ?? ""
self.address = address ?? ""
self.mobilenumber = mobilenumber ?? 0
}
}
//Pass value inside model class
class ViewController: UIViewController {
var model = [BaseModel]() //Model Array Initialization here
override func viewDidLoad() {
super.viewDidLoad()
//Pass value inside Model
model.append(BaseModel(name: "Your name", address: "Your address", mobilenumber: 5545545452))
}
}
//Get value from Model class
class DetailsController: UIViewController {
var details: BaseModel?
override func viewDidLoad() {
super.viewDidLoad()
//Retrive value from model
let name = details?.name ?? ""
let address = details?.address ?? ""
let mobile = details?.mobilenumber ?? 0
}
}