PFQueryTableViewController not loading from Parse into Swift tableView - ios

I am new to Swift and I am trying to find the current user location, and then query all nearby users and then load them into a UITableView in my storyboard. However, when I build my code, no data shows in my UITableView (Parse is my backend). I tried to research the problem and found this way of using PFQueryTableViewController:
PFQueryTableViewController in swift not loading data
However, when I do this
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
always returns an NSException error. How can I fix that problem or fix the below code to return my parse data into my table view?
import UIKit
import Parse
import ParseUI
import CoreLocation
class MasterTableViewController: PFQueryTableViewController {
var usersLocation: PFGeoPoint? {
didSet {
// This will reload the tableview when you set the users location.
// Handy if you want to keep updating it.
if (tableView != nil) {
tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
PFGeoPoint.geoPointForCurrentLocationInBackground { point, error in
if error == nil {
self.usersLocation = point
}
}
}
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "User")
if let location = usersLocation {
query.whereKey("location", nearGeoPoint: location)
}
query.limit = 10
return query
}

In your queryForTable method it seems you're trying to query the User class.
From Parse docs, to query for users there is a special way of doing it:
example:
var query = PFUser.query()
query.whereKey("gender", equalTo:"female")
var girls = query.findObjects()
https://www.parse.com/docs/ios/guide#users-querying
Hopefully that helps

Related

How do refresh my UITableView after reading data from FirebaseFirestore with a SnapShotListener?

UPDATE at the bottom.
I have followed the UIKit section of this Apple iOS Dev Tutorial, up to and including the Saving New Reminders section. The tutorials provide full code for download at the beginning of each section.
But, I want to get FirebaseFirestore involved. I have some other Firestore projects that work, but I always thought that I was doing something not quite right, so I'm always looking for better examples to learn from.
This is how I found Peter Friese's 3-part YT series, "Build a To-Do list with Swift UI and Firebase". While I'm not using SwiftUI, I figured that the Firestore code should probably work with just a few changes, as he creates a Repository whose sole function is to interface between app and Firestore. No UI involved. So, following his example, I added a ReminderRepository.
It doesn't work, but I'm so close. The UITableView looks empty but I know that the records are being loaded.
Stepping through in the debugger, I see that the first time the numberOfRowsInSection is called, the data hasn't been loaded from the Firestore, so it returns 0. But, eventually the code does load the data. I can see each Reminder as it's being mapped and at the end, all documents are loaded into the reminderRepository.reminders property.
But I can't figure out how to get the loadData() to make the table reload later.
ReminderRepository.swift
class ReminderRepository {
let remindersCollection = Firestore.firestore()
.collection("reminders").order(by: "date")
var reminders = [Reminder]()
init() {
loadData()
}
func loadData() {
print ("loadData")
remindersCollection.addSnapshotListener { (querySnapshot, error) in
if let querySnapshot = querySnapshot {
self.reminders = querySnapshot.documents.compactMap { document in
do {
let reminder = try document.data(as: Reminder.self)
print ("loadData: ", reminder?.title ?? "Unknown")
return reminder
} catch {
print (error)
}
return nil
}
}
print ("loadData: ", self.reminders.count)
}
}
}
The only difference from the Apple code is that in the ListDataSource.swift file, I added:
var remindersRepository: ReminderRepository
override init() {
remindersRepository = ReminderRepository()
}
and all reminders references in that file have been changed to
remindersRepository.reminders.
Do I need to provide a callback for the init()? How? I'm still a little iffy on the matter.
UPDATE: Not a full credit solution, but getting closer.
I added two lines to ReminderListViewController.viewDidLoad() as well as the referenced function:
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshTournaments(_:)), for: .valueChanged)
#objc
private func refreshTournaments(_ sender: Any) {
tableView.reloadData()
refreshControl?.endRefreshing()
}
Now, when staring at the initial blank table, I pull down from the top and it refreshes. Now, how can I make it do that automatically?
Firstly create some ReminderRepositoryDelegate protocol, that will handle communication between you Controller part (in your case ReminderListDataSource ) and your model part (in your case ReminderRepository ). Then load data by delegating controller after reminder is set. here are some steps:
creating delegate protocol.
protocol ReminderRepositoryDelegate: AnyObject {
func reloadYourData()
}
Conform ReminderListDataSource to delegate protocol:
class ReminderListDataSource: UITableViewDataSource, ReminderRepositoryDelegate {
func reloadYourData() {
self.tableView.reloadData()
}
}
Add delegate weak variable to ReminderRepository that will weakly hold your controller.
class ReminderRepository {
let remindersCollection = Firestore.firestore()
.collection("reminders").order(by: "date")
var reminders = [Reminder]()
weak var delegate: ReminderRepositoryDelegate?
init() {
loadData()
}
}
set ReminderListDataSource as a delegate when creating ReminderRepository
override init() {
remindersRepository = ReminderRepository()
remindersRepository.delegate = self
}
load data after reminder is set
func loadData() {
print ("loadData")
remindersCollection.addSnapshotListener { (querySnapshot, error) in
if let querySnapshot = querySnapshot {
self.reminders = querySnapshot.documents.compactMap { document in
do {
let reminder = try document.data(as: Reminder.self)
print ("loadData: ", reminder?.title ?? "Unknown")
delegate?.reloadYourData()
return reminder
} catch {
print (error)
}
return nil
}
}
print ("loadData: ", self.reminders.count)
}
}
Please try changing var reminders = [Reminder]() to
var reminders : [Reminder] = []{
didSet {
self.tableview.reloadData()
}
}

Detach from Firestore listener

I'm using Firestore together with Swift.
I have a singleton data class UserManager. I call this from my different ViewControllers to get data to populate my tableviews. I want the tableviews to automatically update when the collections are updated so I need to use a SnapshotListener. Everything works fine but I'm not sure how to detach from the listener when the Viewcontroller is closed.
In the singleton class I have methods like this below. The method gives a list of users and will be called from several different places around my app.
I also want to give back a reference to the listener so that I can detach from it when the Viewcontroller is closed. But I can't get it working. The below solution gives compiler error.
I've been trying to look at the reference, for example here
https://firebase.google.com/docs/firestore/query-data/listen but I need to get it working when the data is loaded in a singleton class instead of directly in the Viewcontroller. What is the way to go here?
In UserManager:
func allUsers(completion:#escaping ([User], ListenerRegistration?)->Void) {
let listener = db.collection("users").addSnapshotListener { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users, listener)
}
}
}
In ViewController:
override func viewDidLoad() {
super.viewDidLoad()
UserManager.shared.allUsers(completion: { (users, listener) in
self.users = users
self.listener = listener
self.tableView.reloadData()
})
}
deinit {
self.listener.remove()
}
I guess the compiler error that you see is referring to the fact that you are using listener into it's own defining context.
Try this for a change:
In UserManager:
func allUsers(completion:#escaping ([User])->Void) -> ListenerRegistration? {
return db.collection("users").addSnapshotListener { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users)
}
}
}
In ViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.listener = UserManager.shared.allUsers(completion: { (users) in
self.users = users
self.tableView.reloadData()
})
}
deinit {
self.listener.remove()
}
I think that getDocument instead of addSnapshotListener is what you are looking for.
Using this method the listener is automatically detached at the end of the request...
It will be something similar to
func allUsers(completion:#escaping ([User])->Void) {
db.collection("users").getDocument { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users)
}
} }

Getting a picture from firebase storage

I'm creating an iOS application which has many pictures on it. I therefore decided to use a database to store the pictures on and retrieve from the database. I have inputted the pictures manually through the Firebase site.
Here is the code I currently have:
import UIKit
import Firebase
class F_A_1: UIViewController {
#IBOutlet weak var imageViewer: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
let database = FIRDatabase.database().reference()
let storage = FIRStorage.storage().reference()
let animalref = FIRStorage.storage().reference().child("animal/bird.png")
animalref.dataWidthMaxSize(1*1000*1000){ (date, error) in
if error = nill {
print(data)
self.imageViewer.image = UIImage(data: data!)
} else {
print(error?.localizedDescription)
}
}
}
This is giving me an error which I cannot fix.
Thanks in advance :)
import UIKit
import Firebase
class F_A_1: UIViewController {
#IBOutlet weak var imageViewer: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
let database = FIRDatabase.database().reference()
let storage = FIRStorage.storage().reference()
let animalref = FIRStorage.storage().reference().child("animal/bird.png")
func nameThisWhatYouWant() {
animalref.data(withMaxSize: 1*1000*1000) { (data, error) in
if error == nil {
print(data)
} else {
print(error?.localizedDescription)
}
}
}
}
i have tried this but it gives a sigbart error
Well I have never worked with Firebase and don't know how to go about connecting it, but you have an issue where you are doing logic in your app.
class F_A_1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
let database = FIRDatabase.database().reference()
let storage = FIRStorage.storage().reference()
let animalref = FIRStorage.storage().reference().child("animal/bird.png")
func nameThisWhatYouWant() {
animalref.dataWidthMaxSize(1*1000*1000){ (date, error) in
if error = nill {
print(data)
self.imageViewer.image = UIImage(data: data!)
} else{
print(error?.localizedDescription)
}
}
}
}
I added a function called nameThisWhatYouWant where you should be handling your data, and sending it off to Firebase. As far as working with Firebase, go here to see how to set up Firebase by working with Cocoa Pods, then allowing you to import Firebase. As far as actually being able to send your data off, they have tons of documentation and examples to follow to read through.
You are comparing the error to nill instead of nil.
Where are you getting your error?

FIRDatabaseReference returns nil, UserID retrievable though

I'm trying to retrieve data from my Firebase database, but I'm stuck at the database reference returning nil. However, I successfully can retrieve the UserID, so I don't think it's not properly connected to my database:
import UIKit
import Firebase
import FirebaseDatabaseUI
class LoggedInViewController: UIViewController {
var ref: FIRDatabaseReference!
let userid = FIRAuth.auth()?.currentUser?.uid
override func viewDidLoad() {
super.viewDidLoad()
UIDLabel.text = String((userid)!) // The Label shows the UID
reflabel.text = String(ref) // The Label shows "nil"
}
}
My goal is to retrieve data from the path using this method:
func getQuery() -> FIRDatabaseQuery {
let myTopPostsQuery = (ref.child("user-posts")).child(getUid()))
return myTopPostsQuery
}
But obviously this doesn't work since I'm getting an error because of the nil reference.
In viewDidLoad use FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference()
//....
}

Displaying geopoint of all users in a map parse

I'am trying to query an array of PFGeoPoints stored on the Parse backend. I have the User table, with data assigned to it such as "location", "name".
everything is being sent to Parse upon posting from my app and is properly stored in the backend. I am having issues retrieving all location from Parse and storing them into an MKAnnotation on the map.
Find below my code
import UIKit
import Parse
import CoreLocation
import MapKit
class mapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapUsers: MKMapView!
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
var currentLoc: PFGeoPoint! = PFGeoPoint()
override func viewDidLoad() {
super.viewDidLoad()
// ask user for their position in the map
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint?, error: NSError?) -> Void in
if let geoPoint = geoPoint {
PFUser.currentUser()? ["location"] = geoPoint
PFUser.currentUser()?.save()
}
}
mapUsers.showsUserLocation = true
mapUsers.delegate = self
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
mapUsers.setUserTrackingMode(MKUserTrackingMode.Follow, animated: false)
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "User")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
annotationQuery.whereKey("Location", nearGeoPoint: currentLoc, withinMiles: 10)
annotationQuery.findObjectsInBackgroundWithBlock {
(PFUser, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let myUsers = PFUser as! [PFObject]
for users in myUsers {
let point = users["Location"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapUsers.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
Instead of putting the call in your viewDidAppear() method - as previous commenters said, your location may be nil, returning no results - I would use a tracker of some sort and put it in your didUpdateLocations() MKMapView delegate method. Here, I use GCD's dispatch_once() so that when my location is found for the first time with a reasonable accuracy, my code is executed (here you will put your call to Parse).
Declare GCD's tracker variable
var foundLocation: dispatch_once_t = 0
Now use something like this in your location manager's delegate method didUpdateLocations()
if userLocation?.location?.horizontalAccuracy < 2001 {
dispatch_once(&foundLocation, {
// Put your call to Parse here
})
}
PS. You should also consider doing any updates to UI on the main thread. Parse fetches that data on a background thread and although you might never see a problem, it's both safer and good habit to do any UI changes on main thread. You can do this with the following:
dispatch_async(dispatch_get_main_queue(), {
// Put any UI update code here. For example your pin adding
}

Resources