I am working on an iOS application which tracks user locations and 1) displays them as map annotation objects and 2) displays the data in a table view controller. The data contained in the table view controller is stored on the end user's iOS device as a JSON file. I am having an issue where I cannot get the records within the JSON file to delete even though delete has been enabled in the code for the table view controller.
I need another pair of eyes to look at the code and hopefully tell me what I'm missing.
The code consists of the following files:
MapViewController.swift
PlacesTableViewController.swift
Location.swift
LocationsStorage.swift
Extension to AppDelegate.swift (Only pertinent code included here)
The contents of the files are as follows:
MapViewController.swift *
import UIKit
import MapKit
class MapViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.userTrackingMode = .follow
let annotations = LocationsStorage.shared.locations.map { annotationForLocation($0) }
mapView.addAnnotations(annotations)
NotificationCenter.default.addObserver(self, selector: #selector(newLocationAdded(_:)), name: .newLocationSaved, object: nil)
}
#IBAction func addItemPressed(_ sender: Any) {
guard let currentLocation = mapView.userLocation.location else {
return
}
LocationsStorage.shared.saveCLLocationToDisk(currentLocation)
}
// Delete location pin from mapview
#IBAction func removeItemPressed(_ sender: Any) {
guard let currentLocation = mapView.userLocation.location else {
return
}
LocationsStorage.shared.saveCLLocationToDisk(currentLocation)
}
func annotationForLocation(_ location: Location) -> MKAnnotation {
let annotation = MKPointAnnotation()
annotation.title = location.dateString
annotation.coordinate = location.coordinates
return annotation
}
#objc func newLocationAdded(_ notification: Notification) {
guard let location = notification.userInfo?["location"] as? Location else {
return
}
let annotation = annotationForLocation(location)
mapView.addAnnotation(annotation)}}
PlacesTableViewController.swift *
import UIKit
import UserNotifications
class PlacesTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(newLocationAdded(_:)),
name: .newLocationSaved,
object: nil)
}
#objc func newLocationAdded(_ notification: Notification) {
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return LocationsStorage.shared.locations.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceCell", for: indexPath)
let location = LocationsStorage.shared.locations[indexPath.row]
cell.textLabel?.numberOfLines = 3
cell.textLabel?.text = location.description
cell.detailTextLabel?.text = location.dateString
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 110
}
// Enables swipe-to-delete functionality in table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
LocationsStorage.shared.locations.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
tableView.reloadData()
}
}
}
Location.swift *
import Foundation
import CoreLocation
class Location: Codable {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
return formatter
}()
var coordinates: CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
let latitude: Double
let longitude: Double
let date: Date
let dateString: String
let description: String
init(_ location: CLLocationCoordinate2D, date: Date, descriptionString: String) {
latitude = location.latitude
longitude = location.longitude
self.date = date
dateString = Location.dateFormatter.string(from: date)
description = descriptionString
}
convenience init(visit: CLVisit, descriptionString: String) {
self.init(visit.coordinate, date: visit.arrivalDate, descriptionString: descriptionString)
}
}
LocationsStorage.swift *
import Foundation
import CoreLocation
class LocationsStorage {
static let shared = LocationsStorage()
//private(set) var locations: [Location]
var locations: [Location]
let fileManager: FileManager
// documentsURL cannot be private expression.
let documentsURL: URL
init() {
let fileManager = FileManager.default
// Location JSON file is stored in user's home directory on iOS. Inaccessible to PC/Mac hardware but can be accessed by application.
documentsURL = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
self.fileManager = fileManager
let jsonDecoder = JSONDecoder()
let locationFilesURLs = try! fileManager.contentsOfDirectory(at: documentsURL,
includingPropertiesForKeys: nil)
locations = locationFilesURLs.compactMap { url -> Location? in
guard !url.absoluteString.contains(".DS_Store") else {
return nil
}
guard let data = try? Data(contentsOf: url) else {
return nil
}
return try? jsonDecoder.decode(Location.self, from: data)
}.sorted(by: { $0.date < $1.date })
}
// Saves location data to JSON object within user directory
func saveLocationOnDisk(_ location: Location) {
let encoder = JSONEncoder()
let timestamp = location.date.timeIntervalSince1970
let fileURL = documentsURL.appendingPathComponent("\(timestamp)")
let data = try! encoder.encode(location)
try! data.write(to: fileURL)
locations.append(location)
NotificationCenter.default.post(name: .newLocationSaved, object: self, userInfo: ["location": location])
}
func saveCLLocationToDisk(_ clLocation: CLLocation) {
let currentDate = Date()
AppDelegate.geoCoder.reverseGeocodeLocation(clLocation) { placemarks, _ in
if let place = placemarks?.first {
let location = Location(clLocation.coordinate, date: currentDate, descriptionString: "\(place)")
self.saveLocationOnDisk(location)
}
}
}
}
extension Notification.Name {
static let newLocationSaved = Notification.Name("newLocationSaved")
}
Extension to AppDelegate.swift *
// Journaling-specific variables
static let geoCoder = CLGeocoder()
let center = UNUserNotificationCenter.current()
let locationManager = CLLocationManager()
... [other code here, not related to question]
extension AppDelegate: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
// create CLLocation from the coordinates of CLVisit
let clLocation = CLLocation(latitude: visit.coordinate.latitude, longitude: visit.coordinate.longitude)
// Get location description
AppDelegate.geoCoder.reverseGeocodeLocation(clLocation) { placemarks, _ in
if let place = placemarks?.first {
let description = "\(place)"
self.newVisitReceived(visit, description: description)
}
}
}
func newVisitReceived(_ visit: CLVisit, description: String) {
let location = Location(visit: visit, descriptionString: description)
LocationsStorage.shared.saveLocationOnDisk(location)
let content = UNMutableNotificationContent()
content.title = "New Location Logged 📌"
content.body = location.description
content.sound = UNNotificationSound.default
// UNTimeIntervalNotificationTrigger is set to 10 minutes. User must spend 10 minutes at a location before a notificatin will be triggered and a log will be created.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (10*60), repeats: false)
let request = UNNotificationRequest(identifier: location.dateString, content: content, trigger: trigger)
center.add(request, withCompletionHandler: nil)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
AppDelegate.geoCoder.reverseGeocodeLocation(location) { placemarks, _ in
if let place = placemarks?.first {
let description = "Fake visit: \(place)"
let fakeVisit = FakeVisit(coordinates: location.coordinate, arrivalDate: Date(), departureDate: Date())
self.newVisitReceived(fakeVisit, description: description)
}
}
}
}
final class FakeVisit: CLVisit {
private let myCoordinates: CLLocationCoordinate2D
private let myArrivalDate: Date
private let myDepartureDate: Date
override var coordinate: CLLocationCoordinate2D {
return myCoordinates
}
override var arrivalDate: Date {
return myArrivalDate
}
override var departureDate: Date {
return myDepartureDate
}
init(coordinates: CLLocationCoordinate2D, arrivalDate: Date, departureDate: Date) {
myCoordinates = coordinates
myArrivalDate = arrivalDate
myDepartureDate = departureDate
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Interestingly, when I am able to delete the records within the table view controller, they appear to delete until the next time the app launches, at which time they are back and still visible.
Any help would be appreciated.
As paulw11 stated in comments, needed to save JSON to disk after performing delete action.
Related
The question might not sound correctly worded but I don't know how to else put it, Apologies.
I am using Google Maps API in my project, to fetch the results and put them in a CollectionView using the MSPeekCollectionViewDelegateImplementation POD
Please any help is so much appreciated!
Always on the first run, as seen in the picture below, it never shows up.
On the second run however it works perfectly, as seen below:
API manager class:
import Foundation
import CoreData
class GoogleMapsAPIManager {
var bloodBanksArray = [BloodBanksModel]()
func fetchCity(latitude: CLLocationDegrees, longitude: CLLocationDegrees, raduis: Double){
let urlString = "\(K.googleMapsAPI)&location=\(latitude),\(longitude)&radius=\(raduis)&key=\(K.apiKey)"
print(urlString)
performRequest(with: urlString)
}
//MARK: - Decoding JSON
func performRequest(with urlString: String){
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(bloodBankData: safeData)
}
}
task.resume()
}
}
func parseJSON(bloodBankData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(BloodBanksData.self, from: bloodBankData)
for i in 0...decodedData.results.count - 1 {
bloodBanksArray.append(BloodBanksModel(name: decodedData.results[i].name, photo: decodedData.results[i].photos?[0].photoReference ?? "ATtYBwJjqXlw3DMdL74SRtgG_GRA3LkAET6hDJXHWtkQMaOTo1B3Gx9jrDTXFLFabGStSxX8BiYdLAnknF7A9ynw33KKyUh5Oc55A9vXzo_6nd4mnk5Sx-iOqMNaw21dk0C424PWaio9GiogQaKecYxOT1q-bmj30syypZmyxkjF7r3-gFyC", open_now: decodedData.results[i].openingHours?.openNow ?? false, longitude: decodedData.results[i].geometry.location.lng, latitude: decodedData.results[i].geometry.location.lat, vincinity: decodedData.results[i].vicinity, rating: decodedData.results[i].rating, placeId: decodedData.results[i].placeId, lat: decodedData.results[i].geometry.location.lat, lng: decodedData.results[i].geometry.location.lng))
}
print("bossins \(bloodBanksArray)")
} catch {
print(error)
}
}
}
Blood banks Model:
import Foundation
struct BloodBanksModel {
let name: String
let photo: String
let open_now: Bool
let longitude: Double
let latitude: Double
let vincinity: String
let rating: Double
let placeId: String
let lat: Double
let lng: Double
}
Blood Bank Data:
import Foundation
// MARK: - BloodBanksData
struct BloodBanksData: Codable {
let results: [Result]
enum CodingKeys: String, CodingKey {
case results
}
}
// MARK: - Result
struct Result: Codable {
let geometry: Geometry
let name: String
let openingHours: OpeningHours?
let photos: [Photo]?
let rating: Double
let vicinity: String
let placeId: String
enum CodingKeys: String, CodingKey {
case geometry, name
case openingHours = "opening_hours"
case photos
case rating
case vicinity
case placeId = "place_id"
}
}
// MARK: - Geometry
struct Geometry: Codable {
let location: Location
}
// MARK: - Location
struct Location: Codable {
let lat, lng: Double
}
// MARK: - OpeningHours
struct OpeningHours: Codable {
let openNow: Bool
enum CodingKeys: String, CodingKey {
case openNow = "open_now"
}
}
// MARK: - Photo
struct Photo: Codable {
let photoReference: String
enum CodingKeys: String, CodingKey {
case photoReference = "photo_reference"
}
}
The VC:
let locationManager = CLLocationManager()
var googleMapsAPIManager = GoogleMapsAPIManager()
override func viewDidLoad() {
super.viewDidLoad()
//locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
var currentLocation: CLLocation!
if
CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways
{
currentLocation = locationManager.location
googleMapsAPIManager.fetchCity(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude, raduis: 2000.0)
}
configureCollectionView()
}
func configureCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
let cellNib = UINib(nibName: K.BloodBanks.bloodBanksCellNibName, bundle: nil)
collectionView.register(cellNib,
forCellWithReuseIdentifier: K.BloodBanks.bloodBankCellIdentifier)
behavior = MSCollectionViewPeekingBehavior(cellSpacing: 20)
behavior = MSCollectionViewPeekingBehavior(cellPeekWidth: 40)
behavior = MSCollectionViewPeekingBehavior(minimumItemsToScroll: 1)
behavior = MSCollectionViewPeekingBehavior(maximumItemsToScroll: 3)
behavior = MSCollectionViewPeekingBehavior(numberOfItemsToShow: 1)
collectionView.configureForPeekingBehavior(behavior: behavior)
}
}
// MARK: - UICollectionViewDataSource Methods
extension LandingViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return googleMapsAPIManager.bloodBanksArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: K.BloodBanks.bloodBankCellIdentifier, for: indexPath) as! BloodBanksCell
DispatchQueue.main.async {
cell.bloodBankName.text = self.googleMapsAPIManager.bloodBanksArray[indexPath.row].name
cell.bloodBankImageView.sd_setImage(with: URL(string: "\(K.googleMapsImageAPI)&photoreference=\(self.googleMapsAPIManager.bloodBanksArray[indexPath.row].photo)&key=\(K.apiKey)"), placeholderImage: #imageLiteral(resourceName: "bloodbank4"))
}
return cell
}
}
// MARK: - UICollectionViewDelegate Methods
extension LandingViewController: UICollectionViewDelegate {
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
behavior.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
Im new in coding. is there a way to show multiple drivers around the user.
.
I've already figured out how to show a list of drivers near the user thru database, now I want the map to show the drivers near the user.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import MapKit
class EmployeeTableViewController: UITableViewController, CLLocationManagerDelegate {
#IBOutlet weak var jobsAvailableMap: MKMapView!
var jobRequests : [DataSnapshot] = []
var locationManager = CLLocationManager()
var employeeLocation = CLLocationCoordinate2D()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
Database.database().reference().child("JobRequests").observe(.childAdded) { (snapshot) in
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let employeeLat = jobRequestDictionary["employeeLat"] as? Double {
} else {
self.jobRequests.append(snapshot)
self.tableView.reloadData()
}
}
}
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { (timer) in
self.tableView.reloadData()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let coord = manager.location?.coordinate {
employeeLocation = coord
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return jobRequests.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobRequestCell", for: indexPath)
let snapshot = jobRequests[indexPath.row]
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
let employeeCLLocation = CLLocation(latitude: employeeLocation.latitude, longitude: employeeLocation.longitude)
let employerCLLocation = CLLocation(latitude: lat, longitude: lon)
let distance = employeeCLLocation.distance(from: employerCLLocation) / 1000
let roundedDistance = round(distance * 100) / 100
cell.textLabel?.text = "\(email) - \(roundedDistance)km away"
}
}
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let snapshot = jobRequests[indexPath.row]
performSegue(withIdentifier: "acceptSegue", sender: snapshot)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let acceptVC = segue.destination as? AcceptJobViewController {
if let snapshot = sender as? DataSnapshot {
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
acceptVC.requestEmail = email
let location = CLLocationCoordinate2D(latitude: lat, longitude: lon)
acceptVC.requestLocation = location
acceptVC.employeeLocation = employeeLocation
}
}
}
}
}
}
}
#IBAction func logoutTapped(_ sender: Any) {
try? Auth.auth().signOut()
navigationController?.dismiss(animated: true, completion: nil)
}
}
I'm trying to look for tutorials online but most are not connected to Firebase Database.
You can use addAnnotation method.
e.g. (no guarantee you can build the following code)
func addDriverAnnotation(snapshot: DataSnapshot){
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
let annotation = MKAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(lat, lon)
annotation.title = "title"
annotation.subtitle = "subtitle"
self.jobsAvailableMap.addAnnotation(annotation)
}
}
}
}
}
You need to call this method in Database.database()... method.
Database.database().reference().child("JobRequests").observe(.childAdded) { (snapshot) in
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let employeeLat = jobRequestDictionary["employeeLat"] as? Double {
} else {
self.jobRequests.append(snapshot)
self.tableView.reloadData()
self.addDriverAnnotation(snapshot: snapshot)
}
}
}
I am doing a projects by following this tutorial enter link description here in swift3 Xcode 8.2.1.
I want to get 10 Nearest Restaurants from my current location.
But Still I can't get any data as map pins or tableview.
Please help me I am new to Swift.
Please find whole coding here including pod file.
Pod File
'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
target "FSResturant" do
pod 'QuadratTouch',
:git => 'https://github.com/Constantine-Fry/das-quadrat', :branch => 'fry-swift30'
pod 'RealmSwift'
end
ResturantAPI.swift
import Foundation
import QuadratTouch
import MapKit
import Realm
import RealmSwift
//Create Venues Struct
struct API
{
struct notifications
{
static let venuesUpdated = "venues_updated"
}
}
class ResturantAPI
{
static let sharedInstance = ResturantAPI()
var session:Session?
init()
{
// Initialize the Foursquare client API Keys
let client = Client(clientID: "XXXX", clientSecret: "XXXX", redirectURL: "")
let configuration = Configuration(client:client)
Session.setupSharedSessionWithConfiguration(configuration)
self.session = Session.sharedSession()
}
//getting venue data from FOURSQUARE
func getResturantsWithLocation(location:CLLocation)
{
//function body
if let session = self.session
{
//this category Id will receive nearby halal resturants in dubai
var parameters = location.parameters()
parameters += [Parameter.categoryId: "52e81612bcbc57f1066b79ff"]
parameters += [Parameter.radius: "2000"]
parameters += [Parameter.limit: "10"]
// Start a "search", i.e. an async call to Foursquare that should return venue data
let searchTask = session.venues.search(parameters)
{
(result) -> Void in
if let response = result.response
{
if let venues = response["venues"] as? [[String: AnyObject]]
{
autoreleasepool
{
let realm = try! Realm()
realm.beginWrite()
for venue:[String: AnyObject] in venues
{
let venueObject:Venue = Venue()
if let id = venue["id"] as? String
{
venueObject.id = id
}
if let name = venue["name"] as? String
{
venueObject.name = name
}
if let location = venue["location"] as? [String: AnyObject]
{
if let longitude = location["lng"] as? Float
{
venueObject.longitude = longitude
}
if let latitude = location["lat"] as? Float
{
venueObject.latitude = latitude
}
if let formattedAddress = location["formattedAddress"] as? [String]
{
venueObject.address = formattedAddress.joined(separator: " ")
}
}
realm.add(venueObject, update: true)
}
do {
try realm.commitWrite()
print("Committing write...")
}
catch (let e)
{
print("Y U NO REALM ? \(e)")
}
}
NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: API.notifications.venuesUpdated), object:nil, userInfo:nil)
}
}
}
searchTask.start()
}
}
}
extension CLLocation
{
func parameters() -> Parameters
{
let ll = "\(self.coordinate.latitude),\(self.coordinate.longitude)"
// let near = "Dubai"
let llAcc = "\(self.horizontalAccuracy)"
// let llAcc = "10000.0"
let alt = "\(self.altitude)"
// let alt = "0"
let altAcc = "\(self.verticalAccuracy)"
// let altAcc = "10000.0"
// let query = "resturants"
let parameters = [
Parameter.ll:ll,
// Parameter.near:near,
Parameter.llAcc:llAcc,
Parameter.alt:alt,
Parameter.altAcc:altAcc,
// Parameter.query:query
]
return parameters
}
}
ViewController.swift
import UIKit
import MapKit
import RealmSwift
import Realm
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
//Initialize the Map View
#IBOutlet var mapView:MKMapView?
//To get the user location
var locationManager:CLLocationManager?
//Span view in meters default
let distanceSpan:Double = 500
//show user's location on the map
var lastLocation:CLLocation?
// Stores venues from Realm, as a non-lazy list
var venues:[Venue]?
//Initializing the table view
#IBOutlet var tableView1:UITableView?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.onVenuesUpdated(_:)), name: NSNotification.Name(rawValue: API.notifications.venuesUpdated), object: nil);
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
//Initializing mapview delegate to display
if let mapView = self.mapView
{
mapView.delegate = self
}
//Initializing UITableview delegate to display
if let tableView1 = self.tableView1
{
tableView1.delegate = self
tableView1.dataSource = self
}
}
override func viewDidAppear(_ animated: Bool)
{
if locationManager == nil
{
locationManager = CLLocationManager()
locationManager!.delegate = self
locationManager!.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager!.requestAlwaysAuthorization()
// Don't send location updates with a distance smaller than 50 meters between them
locationManager!.distanceFilter = 50
locationManager!.startUpdatingLocation()
}
}
// UITable View Delegates
func tableView(_ tableView1: UITableView, numberOfRowsInSection section: Int) -> Int
{
return venues?.count ?? 0
}
func numberOfSections(in tableView1: UITableView) -> Int {
return 1
}
func tableView(_ tableView1: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView1.dequeueReusableCell(withIdentifier: "cellIdentifier");
if cell == nil
{
cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cellIdentifier")
}
if let venue = venues?[indexPath.row]
{
cell!.textLabel?.text = venue.name
cell!.detailTextLabel?.text = venue.address
}
return cell!
}
func tableView(_ tableView1: UITableView, didSelectRowAt indexPath: IndexPath) {
if let venue = venues?[indexPath.row]
{
let region = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2D(latitude: Double(venue.latitude), longitude: Double(venue.longitude)), distanceSpan, distanceSpan)
mapView?.setRegion(region, animated: true)
}
}
func refreshVenues(_ location: CLLocation?, getDataFromFoursquare:Bool = false)
{
if location != nil
{
lastLocation = location
}
if let location = lastLocation
{
if getDataFromFoursquare == true
{
ResturantAPI.sharedInstance.getResturantsWithLocation(location: location)
}
// Convenience method to calculate the top-left and bottom-right GPS coordinates based on region (defined with distanceSpan)
let (start, stop) = calculateCoordinatesWithRegion(location);
// Set up a predicate that ensures the fetched venues are within the region
let predicate = NSPredicate(format: "latitude < %f AND latitude > %f AND longitude > %f AND longitude < %f", start.latitude, stop.latitude, start.longitude, stop.longitude);
let realm = try! Realm()
venues = realm.objects(Venue.self).filter(predicate).sorted {
location.distance(from: $0.coordinate) < location.distance(from: $1.coordinate);
};
for venue in venues!
{
let annotation = ResturantAnnotation(title: venue.name, subtitle: venue.address, coordinate: CLLocationCoordinate2D(latitude: Double(venue.latitude), longitude: Double(venue.longitude)));
mapView?.addAnnotation(annotation);
}
// RELOAD ALL THE DATAS !!!
tableView1?.reloadData()
}
}
//Delegate Method of CLLocation Manager
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation)
{
if let mapView = self.mapView
{
let region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, distanceSpan, distanceSpan)
mapView.setRegion(region, animated: true)
refreshVenues(newLocation, getDataFromFoursquare: true)
}
}
//Adds Annotations in the Map View
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation.isKind(of: MKUserLocation.self)
{
return nil
}
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "annotationIdentifier");
if view == nil
{
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "annotationIdentifier")
}
view?.canShowCallout = true
return view
}
func onVenuesUpdated(_ notification:Foundation.Notification)
{
// When new data from Foursquare comes in, reload from local Realm
refreshVenues(nil);
}
func calculateCoordinatesWithRegion(_ location:CLLocation) -> (CLLocationCoordinate2D, CLLocationCoordinate2D)
{
let region = MKCoordinateRegionMakeWithDistance(location.coordinate, distanceSpan, distanceSpan);
var start:CLLocationCoordinate2D = CLLocationCoordinate2D();
var stop:CLLocationCoordinate2D = CLLocationCoordinate2D();
start.latitude = region.center.latitude + (region.span.latitudeDelta / 2.0);
start.longitude = region.center.longitude - (region.span.longitudeDelta / 2.0);
stop.latitude = region.center.latitude - (region.span.latitudeDelta / 2.0);
stop.longitude = region.center.longitude + (region.span.longitudeDelta / 2.0);
return (start, stop);
}
Venue.swift
import Foundation
import RealmSwift
import MapKit
class Venue: Object
{
dynamic var id:String = ""
dynamic var name:String = ""
dynamic var latitude:Float = 0
dynamic var longitude:Float = 0
dynamic var address:String = ""
var coordinate:CLLocation {
return CLLocation(latitude: Double(latitude), longitude: Double(longitude));
}
//The the primary key to Realm
override static func primaryKey() -> String?
{
return "id";
}
}
ResturantAnnotation.swift
import Foundation
import MapKit
class ResturantAnnotation: NSObject, MKAnnotation
{
let title:String?
let subtitle:String?
let coordinate: CLLocationCoordinate2D
init(title:String?, subtitle:String?, coordinate:CLLocationCoordinate2D)
{
self.title=title
self.subtitle=subtitle
self.coordinate=coordinate
super.init();
}
}
Can Anybody figure out what is the mistake here, that not showing responses in the map and tableview.
Thanks.
So when I select an item from the tableview, it segues into another viewcontroller and its passes data through that, I have it set that I segue back into the parent view controller. however it loses that data from the tableview, I tried to re-run the method that loads it but that doesn't seem to work, ideas as to what I am missing? I try to re-run methods that I think will do the job, but I'm not sure.
var locationManager = CLLocationManager()
var point = PFGeoPoint(latitude: 0.0, longitude: 0.0)
var vList = [Details]()
#IBOutlet var vListTableView: UITableView!
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = self.vListTableView.dequeueReusableCell(withIdentifier: "venueDetailCell",for: indexPath) as! DetailsTableViewCell
cell.Distance.text = String(vList[indexPath.row].distance)
cell.title.text = vList[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return vList.count
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
downloadDataFromDB(location: point)
self.vListTableView.reloadData()
locationManager.startUpdatingLocation()
}
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "vDetailToCommentList"){
var selectedRowIndex = self.vListTableView.indexPathForSelectedRow
let moveViewCont:CommentsViewController = segue.destination as! CommentsViewController
moveViewCont.Test = "Data pass successful"
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0]
let latitude: CLLocationDegrees = userLocation.coordinate.latitude
let longitude: CLLocationDegrees = userLocation.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span: MKCoordinateSpan=MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let location: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region: MKCoordinateRegion = MKCoordinateRegion(center: location, span: span)
map.setRegion(region, animated: true)
map.showsUserLocation = true
point = PFGeoPoint(latitude:latitude, longitude:longitude)
updateUserLocationinDB(location: point)
downloadDataFromDB(location: point)
}
func updateUserLocationinDB(location: PFGeoPoint){
let uAct = PFQuery(className:"uAct")
uAct.whereKey("userId", equalTo:(PFUser.current()?.username)!)
uAct.findObjectsInBackground(block: {(objects, error) in
if error != nil{
print(error)
}else if let users = objects {
if objects?.count>0{
for objects in users{
objects["UserLocation"] = location
objects.saveInBackground()
}
}
}
})
}
func downloadDataFromDB(location: PFGeoPoint){
locationManager.stopUpdatingLocation()
vList.removeAll()
let qHotSpots = PFQuery(className: "HotSpots")
qHotSpots.whereKey("venueLocation", nearGeoPoint: location, withinMiles: 10)
do{
let qReply = try qHotSpots.findObjects()
if qReply.count>10{
for object in qReply{
let curDetails:Details = Details()
let name:String = object.object(forKey:"venue")! as! String
let id:String = object.objectId!
let distance:Double = 0.0
curDetails.name = name
print("vList size",self.vList.count)
}
}
else if qReply.count == 0{
//TODO =: Download from API
}
}
catch{
print(error)
}
}
class Details {
var id:String = ""
var name:String = ""
var distance:Double = 0.0
func Details(iD: String,nam: String,dist: Double){
self.id = iD
self.name = nam
self.distance = dist
}
Maybe I am missing something?
Here's what I have tried
After thinking about when I should update the vListTableView: UITableView!, it makes sense that it should be updated immediately after populating vList. So it makes enough sense to me that the self.vListTableView.reloadData() should be called at the end of downloadDataFromDB as such:
func downloadDataFromDB(location: PFGeoPoint){
locationManager.stopUpdatingLocation()
vList.removeAll()
let qHotSpots = PFQuery(className: "HotSpots")
qHotSpots.whereKey("venueLocation", nearGeoPoint: location, withinMiles: 10)
do{
let qReply = try qHotSpots.findObjects()
if qReply.count>10{
for object in qReply{
let curDetails:Details = Details()
let name:String = object.object(forKey:"venue")! as! String
let id:String = object.objectId!
let distance:Double = 0.0
curDetails.name = name
//print("vList size",self.vList.count)
}
//THE FIX WAS HERE
self.venueListTableView.reloadData()
}
else if qReply.count == 0{
//TODO =: Download from API
}
}
catch{
print(error)
}
This way, reloaddata is always called at the end of downloadDataFromDB and it is never needed anywhere else.
Like clockwork.
HERE is the latest code. I've tried moving the tableView.reloadData() all over the place - anyone have any ideas or suggestions? I am still staring at an empty table that says "no results" when i make my API call and get exactly what I need in return.
I have also reset the constraints all over my storyboard - I feel like I am missing something very simple here
import UIKit
import Alamofire
import MapKit
//import CoreLocation
class ToolTableViewController: UITableViewController, CLLocationManagerDelegate {
// #IBOutlet weak var tableview: UITableView!
var jsonArray:NSMutableArray?
var tools = [Tool]()
#IBOutlet weak var toolListSearchBar: UISearchBar!
let locationManager = CLLocationManager()
var currentLat: CLLocationDegrees = 0.0
var currentLong: CLLocationDegrees = 0.0
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
// print("locations = \(locValue.latitude) \(locValue.longitude)")
let location = locations.last! as CLLocation
currentLat = location.coordinate.latitude
currentLong = location.coordinate.longitude
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() })
self.tableView.registerClass(ToolTableViewCell.self, forCellReuseIdentifier: "ToolTableViewCell")
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
else{
print("Location service disabled");
}
// self.tableView.reloadData()
// Load the sample data.
}
func refresh(sender: AnyObject) {
// Reload the data
self.tableView.reloadData()
}
func searchBarSearchButtonClicked(searchbar: UISearchBar)
{
searchbar.resignFirstResponder()
tools = []
let defaults = NSUserDefaults.standardUserDefaults()
let userid: Int = defaults.objectForKey("toolBeltUserID") as! Int
let searchTerm = String(toolListSearchBar.text!)
print(searchTerm)
Alamofire.request(.GET, "http://localhost:3000/tools/search", parameters: ["keyword": searchTerm, "latitude": currentLat, "longitude": currentLong,
"user": userid]) .responseJSON {response in
if let JSON = response.result.value {
print("\(JSON)")
for i in 0 ..< JSON.count {
let owner = JSON[i].objectForKey("owner")
let tool = JSON[i].objectForKey("tool")
let title = tool!["title"] as! String!
let ownerId = owner!["id"] as! Int!
let distanceToTool = JSON[i].objectForKey("distance") as! Double
var description: String
if let des = tool!["description"] as? NSNull {
description = ""
} else {
description = (tool!["description"] as? String!)!
}
let myTool = Tool(title: title!, description: description, ownerId: ownerId!, distance: distanceToTool)
self.tools.append(myTool)
}
} else {
print("Sent search term, but no response")
}
self.refresh(self)
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tools.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ToolTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ToolTableViewCell
let tool = tools[indexPath.row]
cell.title?.text = tool.title
// print(tool.title)
// cell.toolListDescription?.text = tool.description
cell.ownerId = tool.ownerId
// print(tool.ownerId)
// print(tool.distance)
// cell.distance?.text = "\(tool.distance)mi"
print(cell)
return cell
print(cell)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The call to reloadData belongs inside the completion closure for the Alamofire request.
Where have it now it's getting called before the Alamofire request call is complete.
I don't remember if Alamofire's completion closures are run on the main thread or on a background thread. If they're run on a background thread then you'll need to wrap the call to reloadData in a dispatch_async(dispatch_get_main_queue){ } call.
EDIT:
I said the call to reloadData belongs inside the closure for the Alamofire request call. Like this:
Alamofire.request(.GET, "http://localhost:3000/tools/search", parameters: ["keyword": searchTerm, "latitude": currentLat, "longitude": currentLong,
"user": userid]) .responseJSON {response in
if let JSON = response.result.value {
print("\(JSON)")
for i in 0 ..< JSON.count {
let owner = JSON[i].objectForKey("owner")
let tool = JSON[i].objectForKey("tool")
let title = tool!["title"] as! String!
let ownerId = owner!["id"] as! Int!
let distanceToTool = JSON[i].objectForKey("distance") as! Double
var description: String
if let des = tool!["description"] as? NSNull {
description = ""
} else {
description = (tool!["description"] as? String!)!
}
let myTool = Tool(title: title!, description: description, ownerId: ownerId!, distance: distanceToTool)
self.tools.append(myTool)
}
//This call is inside the closure for the request call.
dispatch_async(dispatch_get_main_queue())
{
//Call reloadData from the main thread
self.tableView.reloadData()
//Or you could call your refresh method.
}
} else {
print("Sent search term, but no response")
}
}
}