TwitterKit - TWTRSearchTimelineDataSource Search Query Formatting - ios

I am retrieving and displaying a list of tweets in a table view. The following code grabs a bunch of tweets from a search and displays them. This works fine!
Twitter.sharedInstance().logInGuestWithCompletion { session, error in
if let _ = session {
let client = Twitter.sharedInstance().APIClient
self.dataSource = TWTRSearchTimelineDataSource(searchQuery: "cats", APIClient: client)
} else {
print("error: \(error.localizedDescription)")
}
}
However, when I change the search query in order to get tweets referring to a specific user, I just get a blank screen.
Twitter.sharedInstance().logInGuestWithCompletion { session, error in
if let _ = session {
let client = Twitter.sharedInstance().APIClient
self.dataSource = TWTRSearchTimelineDataSource(searchQuery: "#mtasummit", APIClient: client)
} else {
print("error: \(error.localizedDescription)")
}
}
I've trying to encode the '#' symbol a couple of ways, and that didn't seem to work either. What am I doing wrong?

I overlooked a small detail in the API:
"Also note that the search results at twitter.com may return historical results while the Search API usually only serves tweets from the past week." (https://dev.twitter.com/rest/public/search)
The reason I was not seeing any tweets, was because my search term "#mtasummit" only contained results several months old, so they weren't being retrieved.

Related

Getting Information from Two Firebase Root Collections

I am using Firebase Firestore with Swift and SwiftUI. I have two collections Users and Tweets. Each Tweet store the userId of the user who tweeted. I want to fetch all the tweets along with the user information associated with tweet. Since both are stored in different collections at the root level, this poses a problem. Here is one of the solutions.
private func populateUserInfoIntoTweets(tweets: [Tweet]) {
var results = [Tweet]()
tweets.forEach { tweet in
var tweetCopy = tweet
self.db.collection("users").document(tweetCopy.userId).getDocument(as: UserInfo.self) { result in
switch result {
case .success(let userInfo):
tweetCopy.userInfo = userInfo
results.append(tweetCopy)
if results.count == tweets.count {
DispatchQueue.main.async {
self.tweets = results.sorted(by: { t1, t2 in
t1.dateUpdated > t2.dateUpdated
})
}
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
Kinda messy!
Another way is to store the documentRef of the User doc instead of the userId.
If I store the User document reference then I am using the following code:
init() {
db.collection("tweets").order(by: "dateUpdated", descending: true)
.addSnapshotListener { snapshot, error in
if let error {
print(error.localizedDescription)
return
}
snapshot?.documents.compactMap { document in
try? document.data(as: Tweet.self)
}.forEach { tweet in
var tweetCopy = tweet
tweetCopy.userDocRef?.getDocument(as: UserInfo.self, completion: { result in
switch result {
case .success(let userInfo):
tweetCopy.userInfo = userInfo
self.tweets.append(tweetCopy)
case .failure(let error):
print(error.localizedDescription)
}
})
}
}
}
The above code does not work as expected. I am wondering there must be a better way. I don't want to store all the user information with the Tweet because it will be storing a lot of extra data.
Recommendations?
The common alternative is to duplicate the necessary information of the user in their tweets, a process known as denormalization. If you come from a background in relational databases this may sounds like blasphemy, but it's actually quite common in NoSQL data modeling (and one of the reasons these databases scale to such massive numbers of readers).
If you want to learn more about such data modeling considerations, I recommend checking out NoSQL data modeling and watching Todd's excellent Get to know Cloud Firestore series.
If you're wondering how to keep the duplicated data up to date, this is probably also a good read: How to write denormalized data in Firebase

Parse won't save objects in local datastore after I close application and disconnect from Wi-Fi

I'm trying to save some objects locally so when I close my application and disconnect from my Wi-Fi I'll still be able to have the data but this does not work.
I have created this function to save my database.
func saveAllQueriesLocally() {
let queries = PFQuery(className: "Directory")
PFObject.unpinAllObjectsInBackground()
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
i.pinInBackground()
}
} else {
// The query succeeded but no matching result was found
}
}
}
and I have called this function inside viewDidLoad() of my main ViewController. I am not sure but I am guessing the function searches the database when it is offline and since it does not retrieve anything it rewrites the cache as empty.
While being connected to the internet, objects get retrieved correctly.

Fabric , Digit do not return find friends list

When I upload all contact and ask for any common friends, it just return null. But I have common friends who use digit and if there is no friends, it's just return
[ ]
But now it's return nil. ( Here's the output of below code )
Total contacts: 10, uploaded successfully: 10
Friends: Digits ID: nil
how can I get digit users ? what's the error in my code ( previously this successfully returned common digit users )
In viewDidLoad()
let digits = Digits.sharedInstance().session()
self.uploadDigitsContacts(digits!)
And then the functions :-
private func uploadDigitsContacts(session: DGTSession) {
let digitsContacts = DGTContacts(userSession: session)
digitsContacts.startContactsUploadWithCompletion { result, error in
if result != nil {
// The result object tells you how many of the contacts were uploaded.
print("Total contacts: \(result.totalContacts), uploaded successfully: \(result.numberOfUploadedContacts)")
self.findDigitsFriends(session)
}
}
}
private func findDigitsFriends(session: DGTSession) {
let digitsSession = Digits.sharedInstance().session()
let digitsContacts = DGTContacts(userSession: digitsSession)
// looking up friends happens in batches. Pass nil as cursor to get the first batch.
digitsContacts.lookupContactMatchesWithCursor(nil) { (matches, nextCursor, error) -> Void in
// If nextCursor is not nil, you can continue to call lookupContactMatchesWithCursor: to retrieve even more friends.
// Matches contain instances of DGTUser. Use DGTUser's userId to lookup users in your own database.
print("Friends:")
print("Digits ID: \(matches)")
for digitsUser in matches {
print("Digits ID: \(digitsUser.userID)")
}
}
}
Found the solution !!! When you use the same simulator for upload contacts by using different numbers this error occurs. So try to delete all contacts from that Phone number by using :-
let userSession = Digits.sharedInstance().session()
let contacts = DGTContacts(userSession: userSession)
contacts.deleteAllUploadedContactsWithCompletion { error in
// Inspect error to determine if delete succeeded.
}
Then log out from that device :-
Digits.sharedInstance().logOut()
Then again sign in to Digit and upload contacts again, and request for any friends who are using the app. Don't forget to remove deleteAllUploadedContactWithCompletion when sign in again to digit.

How to create autocomplete text field in Swift

What I want to be able to create is an auto complete text field in iOS.
I have a form for selecting a client, wherein the user must select a client once using a text field . What I want to happen is when the user writes the first three letters on the text field, I want some service to run a remote web service query using the entered text and present the query results as auto complete suggestions.
Below is my current code for my app (iPad only).
import UIKit
class AddClientViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var clientTextField: UITextField!
var foundList = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let listUrlString = "http://bla.com/myTextField.php?field=\(clientTextField)"
let myUrl = NSURL(string: listUrlString);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print(error!.localizedDescription)
dispatch_sync(dispatch_get_main_queue(),{
AWLoader.hide()
})
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray
if let parseJSON = json {
self.foundList = parseJSON as! [String]
}
} catch {
print(error)
}
}
task.resume()
}
Here is the json output that my web service provides.
["123,John", "343,Smith", "345,April"]
Separated by commas, the first parameter is the client ID and the second parameter is the name of the client. John is the name so it should be presented in the auto complete suggestions, which if selected will set the text of the clientTextField to John.
The current text content of the clientTextField is passed as a GET parameter to my webservice.
I don't know how to do this. The user could be typing and not yet finished, while multiple queries could already have been sent.
I did something like this in my app for looking up contacts. I will pseudo code this out for you to understand the concept:
1) Capture the characters entered into the textfield by the enduser
2) At some character count entered decide to query the server to return all entries that match - choose the character count you are comfortable with (I chose around 3-4 characters). Fewer returns more, more returns less obviously...up to you, perf and UX considerations.
3) Put the results of this server query into an array on the client. This will be your superset from which you will offer the suggestions to the user.
4) After each subsequent character entered into the text field you will now filter the array (array.filter()) by character string entered to this point.
5) tableView.reloadData() against the filtered array at each character entered.
6) I use a dataFlag variable to determine what datasource to show in the tableview depending on what the user is doing.
Note: You only query the server once to minimize perf impact
// this function is called automatically when the search control get user focus
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
if searchBar.text?.range(of: "#") != nil {
self.getUserByEmail(searchBar.text!)
}
if searchController.searchBar.text?.characters.count == 0 && dataFlag != "showParticipants" {
dataFlag = "showInitSearchData"
self.contacts.removeAll()
self.participantTableView.reloadData()
}
if dataFlag == "showInitSearchData" && searchController.searchBar.text?.characters.count == 2 {
self.loadInitialDataSet() {
self.dataFlag = "showFilteredSearchData"
}
}
if dataFlag == "showFilteredSearchData" {
self.filterDataForSearchString()
}
}
// filter results by textfield string
func filterDataForSearchString() {
let searchString = searchController.searchBar.text
self.filteredContacts = self.contacts.filter({
(contact) -> Bool in
let contactText: NSString = "\(contact.givenName) \(contact.familyName)" as NSString
return (contactText.range(of: searchString!, options: NSString.CompareOptions.caseInsensitive).location) != NSNotFound
})
DispatchQueue.main.async {
self.participantTableView.reloadData()
}
}
Using Trie like structure will be a better option here. Based on entered string, trie will return top keywords (lets say 10) starting with the entered string. Implementing this trie on server side is better. When UI makes http call, calculations will be done on server side and server will send top results to UI. Then, UI will update TableView with new data.
You can also do this with hashmap/dictionary but performance will be worse. Using trie/prefix tree approach will give you the best performance when you have thousands or millions of strings to check.

How can i extract data from an anyObject in Swift

I'm using the TwitterKit SDK and listing a group of Tweets. The function has an error handler that stores any tweets that have been removed by the user and thus not shown. I am attempting to retrieve these particular ID's from the NSError user info dictionary. I can find them but end up with an anyObject.
This code is getting the tweet objects and filtering out the bad ones...
// load tweets with guest login
Twitter.sharedInstance().logInGuestWithCompletion {
(session: TWTRGuestSession!, error: NSError!) in
// Find the tweets with the tweetIDs
Twitter.sharedInstance().APIClient.loadTweetsWithIDs(tweetIDs) {
(twttrs, error) - > Void in
// If there are tweets do something magical
if ((twttrs) != nil) {
// Loop through tweets and do something
for i in twttrs {
// Append the Tweet to the Tweets to display in the table view.
self.tweetsArray.append(i as TWTRTweet)
}
} else {
println(error)
}
println(error)
if let fails: AnyObject = error.userInfo?["TweetsNotLoaded"] {
println(fails)
}
}
}
The println(error) dump is...
Error Domain=TWTRErrorDomain Code=4 "Failed to fetch one or more of the following tweet IDs: 480705559465713666, 489783592151965697." UserInfo=0x8051ab80 {TweetsNotLoaded=(
480705559465713666,
489783592151965697
), NSLocalizedDescription=Failed to fetch one or more of the following tweet IDs: 480705559465713666, 489783592151965697.}
and refining the results from the error "error.userInfo?["TweetsNotLoaded"]" I can end up with...
(
480705559465713666,
489783592151965697
)
My question is, is there a better way to get this data?
If not, how am I able to convert the (data, data) anyObject into an array of [data, data] ?
Best guess is that TweetsNotLoaded is an NSArray of NSNumber (or maybe NSString, not sure how they're coding/storing message ids), so you can cast the result and go from there:
if let tweetsNotLoaded = error.userInfo?["TweetsNotLoaded"] as? [String] {
// here tweets not loaded will be [String], deal with them however
...
}
If that doesn't work, assume they're longs and use:
if let tweetsNotLoaded = error.userInfo?["TweetsNotLoaded"] as? [Int64] {
// here tweets not loaded will be [Int64], deal with them however
...
}

Resources