Why do I not get back business names when using MKLocalSearch? - swift

For example in a MKLocalSearch query, I set the nautral langauge query to: "Coffee". I get several annotations to drop down where coffee shops are, but I only get addresses, despite them landing on a business name / location. What do I need to include in order to retrieve this data?
Here is what I have so far:
request.naturalLanguageQuery = address
request.region = self.mapView.region
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { (response, error) -> Void in
if error != nil {
print("error: \(error)")
return
}
if response != nil {
self.mapView.removeAnnotations(self.mapView.annotations)
for item in response!.mapItems {
self.plotPlacemarkOnMap(item.placemark)
}
}
}

Related

How can I create a function to check if a user is blocked?

My app is being prepared for apple app store distribution and the last thing that I need to do is allow users to block another user. My idea was that in a user on firebase, there would be a collection called blocked and it would have documents with the name of blocked users, then whenever a user opens a profile, the app checks if the user of that profile is blocked, and if they are blocked, it just shows a title saying that the user is blocked, instead of showing the regular profile info.
Here is what the code that adds the blocked user to the list looks like:
public func BlockUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
.document(username)
ref.setData(["username": username])
}
And that works because this is what shows up in firestore:
So then to do the second part (checking if a profile user is in the blocked list) I use this code:
public var userIsBlocked: Bool = false
public func IsBlockedByUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
.document(username)
ref.getDocument { (document, error) in
if let document = document, document.exists {
self.userIsBlocked = true
} else {
print("Document does not exist")
self.userIsBlocked = false
}
}
}
However, even if a user is on the blocked list when I open their profile, it still prints "Document does not exist.". I used googles sample code to look for the document so why is it not finding it?
I still don't see an issue with the code I had previously used but I replaced it with a query and this seems to have fixed the issue. Here's the new code I'm using to search for if a user is blocked:
public var userIsBlocked: Bool = false
public func UserIsBlocked(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
ref.whereField("username", isEqualTo: username)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
self.userIsBlocked = false
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.userIsBlocked = true
}
}
}
}
public var blockedByUser: Bool = false
public func IsBlockedByUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blockedBy")
ref.whereField("username", isEqualTo: username)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
self.blockedByUser = false
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.blockedByUser = true
}
}
}
}
And then when a user's profile is opened, these functions are called to check if the current user has either blocked the user whose profile they are trying to view or have been blocked by them. Based on the result, the two variables are set to either true or false. If they are both false, the profile loads, and if either of them is true, the profile does not load and shows an alert before returning to the previous view controller. This code is all over the place but it gets the job done!

Firestore Querying a Snapshot within a Snapshot?

I am trying to listen for any notifications whenever someone has replied to a post that a user has commented on. Below is how my database structure looks.
Posts: (Collection)
Post 1: (Document)
replies: [user2, user3]
Replies: (Collection)
Reply 1: (Document)
ownerId: [user2]
Reply 2: (Document)
ownerId: [user3]
Currently my code has 2 snapshot listeners. The first one listens to the Posts collections, where a user is inside the 'replies' array. Then the second one listens to the Replies collection, where it returns all documents added that != the current user. When a new reply has been detected, it will set the Tab Bar item's badge.
This currently works right now, but I am curious if there is a better method of doing so.
func getNotifications() {
database.collection("Posts")
.whereField("replies", arrayContains: userData["userId"]!)
.order(by: "timestamp", descending: true)
.limit(to: 70)
.addSnapshotListener() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { documentd in
if (documentd.type == .added) {
let dataTemp = documentd.document.data()
let ifUser = dataTemp["ownerId"] as! String
if(ifUser == self.userData["userId"]!) {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: dataTemp["timestamp"] as! Int)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
else {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("ownerId", isEqualTo: self.userData["userId"]!)
.order(by: "timestamp", descending: false)
.limit(to: 1)
.getDocuments() { (querySnapshot2, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
var timestamp = Int()
for documentde in querySnapshot2!.documents {
let temp = documentde.data()
timestamp = temp["timestamp"] as! Int
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: timestamp)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
Your code does not have only two listeners, but one listener per post for which the user you are interested in has ever replied for. This will lead to terrible performances very soon and will potentially crash you app as Firestore has a limitation of 100 listeners per client.
I would advise to redesign your data model:
Only one listener on posts that the user has ever replied to (your first listener)
On each reply increment a reply counter in the post doc, this will trigger the snapshot above.
Optimisation 1: on each action on a post you could set a field lastactiontype, which would have a specific value reply for replies only. This way the snapshot is only triggered on replies.
Optimisation 2: set a field timestamp on each action to the current time and only pull the last n (for instance 10) posts in your snapshots, this will limit the number of reads at load time. You will have to implement some special logic to handle the case when your app goes offline and back online and all n posts of the snapshot have changed. This is a must if your app is meant to scale (you dont want a snapshot with no limit on a collection with 100k docs...)
Example:
firestore.collection("Posts")
.where( "lastaction", "==" , "reply")
.where( "replies", "array-contains", uid)
.orderBy("timestamp", "desc").limit(10)

Nil on data request, problem with handling requests from REST API

I'm trying to inflate my layout with data, it works but on some attempts user filed or comments field just have still nil and it fails. How can I can optimise the requests? I'm still beginner with swift and I cannot wrap my had around this.
The class that fails is the findUserById which does just what it is in the name. So find the user for specific id from the users list. But sometimes users are not yet initalized and the error with force unwraping is visible
func findUserByUserId(UserId:Int) -> User{
var userPlaceholder : User!
for user in self.users {
if(user.id == UserId){
userPlaceholder = user
}
}
return userPlaceholder
}
This is the function that I'm using for setting up the structure of the table cell
private func fetchPost() {
self.posts.forEach { (post) in
DispatchQueue.global(qos: .userInteractive).async(group: dispatchGroup) {
self.dispatchGroup.enter()
self.postsCellViewModels.append(PostsCellViewModel(post: post, user: self.findUserByUserId(UserId: post.userId), comments: self.findCommensByPostId(PostId: post.id)))
self.dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
os_log("PostsViewModel -> Finished fetching posts")
self.isLoading = false
self.reloadData?()
}
And this is function for fetchingUsers from API, i have the same for posts and comments, data is taken from JSONPlaceholderAPI
func fetchUsers(){
dispatchGroup.enter()
os_log("PostsViewModel -> Starting users fetching")
if let client = client as? JSONPlaceholderClient {
self.isLoading = true
let endpoint = JsonPlaceHolderEndpoint.users
client.fetchUsers(with: endpoint) { (either) in
switch either {
case .success(let users):
self.users = users
os_log("PostsViewModel -> Ended users fetching")
self.dispatchGroup.leave()
case .error(let error):
self.showError?(error)
}
}
}
}

Swift closures and error handling

hello, i need some help with function and or possibly closures in that function. I want my function to check the firestore users collection for duplicate documents (usernames). If a duplicate is found i want to display a message, if a duplicate is not found, create a new user. i have folowing code:
func checkIfUserExists(username: String, completion: #escaping (Bool) -> Void) {
let docRef = db.collection("users").document(username)
docRef.getDocument { (document, error) in
if error != nil {
print(error)
} else {
if let document = document {
if document.exists {
completion(true)
} else {
completion(false)
}
}
}
}
}
and i call the function with:
if let username = textField.text, username.count > 8 { // Username needs to be more then 8 Char
checkIfUserExists(username: username) { (doesExist) in
if doesExist {
print("user exists")
} else {
print("new User can be created")
}
}
} else {
print("Username needs to be more then 8 char")
}
}
It works, but i have the feeling it is not good practice and i'm making detours. Is this the right way to do it ?
I think the way you're doing it now should work well, but another option to prevent you from having to do a read of the database before writing is to use security rules. For example, if this is the structure of your users collection...
users: [
username1: { // doc ID is the username
userid: abcFirebaseUserId, // a field for the uid of the owner of the username
//...etc
}
]
...then you can use the following rules:
match /users/{username} {
allow create: if request.auth.uid != null;
allow update, delete: if resource.data.userId = request.auth.uid;
}
This allows any authenticated user to create a new username, but only the owner of that username can update it or delete it. If you aren't allowing users to change their username, you wouldn't even have to worry about the second rule. Then, in the client, you go right to creating a username, like so:
func createUsername(username: String, completion: #escaping (String?) -> Void) {
guard let userId = Auth.auth().currentUser.uid else {
completion("no current user")
return
}
let docRef = db.collection("users").document(username)
docRef.setData(data:[userId: userId]) { error in
if let error = error {
completion(error.debugDescription)
} else {
completion(nil)
}
}
}
This would write the new username to the database and pass an error to the closure if there is one. If the username already exists, an insufficient permissions error would be present. When checking if the user exists, you could display the error or alert the user however you wanted.
createUsername(username: username) { err in
if let err = err {
print("user exists")
} else {
print("new User has been created")
}
}
Just a suggestion though. I think they way you're doing it now is fine, too!

CloudKit, retrieve user information, such as first name on sign up

During SignUp on my app, I want to retrieve information, such as first name, from iCloud,I then want to store this in my own cloud kit database. How do I access user information from iCloud, without having to ask the user themselves for these relevant fields?
I was able to get it working with this in XCode 8 iOS 10 beta 2:
CKContainer.default().requestApplicationPermission(.userDiscoverability) { (status, error) in
CKContainer.default().fetchUserRecordID { (record, error) in
CKContainer.default().discoverUserIdentity(withUserRecordID: record!, completionHandler: { (userID, error) in
print(userID?.hasiCloudAccount)
print(userID?.lookupInfo?.phoneNumber)
print(userID?.lookupInfo?.emailAddress)
print((userID?.nameComponents?.givenName)! + " " + (userID?.nameComponents?.familyName)!)
})
}
}
Use CKContainer.discoverUserIdentity(withUserRecordID:) in combination with CKContainer.fetchUserRecordID to get a CKUserIdentity object for the current user. You can then use a PersonNameComponentsFormatter to get their name from the nameComponents property of the identity.
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler { (recordId, error) in
if error != nil {
print("Handle error)")
}else{
self.container.discoverUserInfoWithUserRecordID(
recordId!, completionHandler: { (userInfo, error) in
if error != nil {
print("Handle error")
}else{
if let userInfo = userInfo {
print("givenName = \(userInfo.displayContact?.givenName)")
print("familyName = \(userInfo.displayContact?.familyName)")
}else{
print("no user info")
}
}
})
}
}