I want to search for the key id in the dictionary. I have a dictionary like this:
var tableData:[String:Any] = ["id":["path":"","type":"","parameters":[]]]
Table data has 307 items, and all id's unique. I want to search in dictionary key id, like when I write "get" , it need the show all the search results with "get" in table view.
func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text
if let entry = tableData.keys.first(where: { $0.lowercased().contains(searchString) }) {
print(entry)
} else {
print("no match")
}
tableView.reloadData()
}
func didChangeSearchText(searchText: String) {
if let entry = tableData.keys.first(where: { $0.lowercased().contains(searchText) }) {
print(entry)
} else {
print("no match")
}
// Reload the tableview.
tableView.reloadData()
}
When I try to search a word it prints "no match", in the debug, unable to read data writes for the value of the entry. Thank you in advance!
In fact your keys have to be unique and as in your case id is a top level key you don't have to perform filtering in order to access its value. Simply use tableData[searchText] to grab its value.
If you don't know the id value and you want to loop through all the keys you can do so like
for key in tableData.keys {
print(key)
let value = tableData[key]
// or do whatever else you want with your key value
}
As per what you already have in place you need to do the following
var tableData:[String:Any] = ["hello world":["path":"fgh","type":"dfghgfh","parameters":[]], "something else":["path":"sdfsdfsdf","type":"dfghfghfg","parameters":[]]]
if let entry = tableData.keys.first(where: { $0.lowercased().contains("hello") }) {
print(entry)
// prints 'hello world'
} else {
print("no match")
}
Or you can simply get a new filtered array from your data source like
let result = tableData.filter { (key, value) in
key.lowercased().contains("else") // your search text replaces 'else'
}
/*
* result would be an array with the objects based on your id search
* so you'll not only have the keys but the entire object as well
*/
print("Found \(result.count) matches")
To access an element in a Dictionary with a key, use the following code.
if let entry = tableData[searchText] {
print(entry)
}
For further information, have a look at:
how can i get key's value from dictionary in Swift?
Try using first(where:) directly on tableData, like this:
func updateSearchResults(for searchController: UISearchController) {
guard let searchString = searchController.searchBar.text else { return }
if let (id, entry) = tableData.first(where: { (key, value) -> Bool in key.lowercased().contains(searchString) }) {
print(entry)
} else {
print("no match")
}
tableView.reloadData()
}
func didChangeSearchText(searchText: String) {
if let (id, entry) = tableData.first(where: { (key, value) -> Bool in key.lowercased().contains(searchText) }) {
print(entry)
} else {
print("no match")
}
// Reload the tableview.
tableView.reloadData()
}
Related
I'm trying to populate the Sections and Rows of my tableview using Firestore data that I've parsed and stored inside of a dictionary, that looks like this...
dataDict = ["Monday": ["Chest", "Arms"], "Wednsday": ["Legs", "Arms"], "Tuesday": ["Back"]]
To be frank, I'm not even sure if I'm supposed to store the data inside of a dictionary as I did. Is is wrong to do that? Also, since the data is being pulled asynchronously, how can I populate my sections and rows only after the dictionary is fully loaded with my network data? I'm using a completion handler, but when I try to print the results, of the dataDict, it prints out three arrays in succession, like so...
["Monday": ["Chest", "Arms"]]
["Tuesday": ["Back"], "Monday": ["Chest", "Arms"]]
["Tuesday": ["Back"], "Monday": ["Chest", "Arms"], "Wednsday": ["Legs", "Arms"]]
Whereas I expected it to return a single print of the array upon completion. What am I doing wrong?
var dataDict : [String:[String]] = [:]
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
vcBackgroundImg()
navConAcc()
picker.delegate = self
picker.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
tableView.tableFooterView = UIView()
Auth.auth().addStateDidChangeListener { (auth, user) in
self.userIdRef = user!.uid
self.colRef = Firestore.firestore().collection("/users/\(self.userIdRef)/Days")
self.loadData { (done) in
if done {
print(self.dataDict)
} else {
print("Error retrieving data")
}
}
}
}
//MARK: - Load Data
func loadData(completion: #escaping (Bool) -> ()){
self.colRef.getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
completion(false)
}
else {
//Appending all Days collection documents with a field of "dow" to daysarray...
for dayDocument in snapshot!.documents {
self.daysArray.append(dayDocument.data()["dow"] as? String ?? "")
self.dayIdArray.append(dayDocument.documentID)
Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
completion(false)
}
else {
//Assigning all Workouts collection documents belonging to selected \(dayDocument.documentID) to dictionary dataDict...
for document in snapshot!.documents {
if self.dataDict[dayDocument.data()["dow"] as? String ?? ""] == nil {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""] = [document.data()["workout"] as? String ?? ""]
} else {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""]?.append(document.data()["workout"] as? String ?? "")
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
// print(self.dataDict)
}
completion(true)
}
}
}
self.dayCount = snapshot?.count ?? 0
}
}
}
I think it is just the flow of your program. Every time you loop through the collection, you add what it gets to the dictionary. So on the first pass, it will print that the dictionary has 1 item. On the second pass, it adds another item to the dictionary, and then prints the dictionary, which now has 2 items in it, so 2 items are printed. I don't think you are seeing unexpected behavior, it is just how you have your log statement ordered with how you are looping.
In other words, it makes sense that it is printing like that.
I agree with #ewizard's answer. The problem is in the flow of your program. You iterate through the documents and you fetch the documents in the collection for every iteration. You also reload the tableView and call completion closure multiple times, which you don't want to do.
To improve the flow of your program try using DispatchGroup to fetch your data and then put it together one once, when all the data is fetched. See example below to get the basic idea. My example is a very simplified version of your code where I wanted to show you the important changes you should preform.
func loadData(completion: #escaping (Bool) -> ()) {
self.colRef.getDocuments { (snapshot, err) in
// Handle error
let group = DispatchGroup()
var fetchedData = [Any]()
// Iterate through the documents
for dayDocument in snapshot!.documents {
// Enter group
group.enter()
// Fetch data
Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
// Add your data to fetched data here
fetchedData.append(snapshot)
// Leave group
group.leave()
}
}
// Waits for until all data fetches are finished
group.notify(queue: .main) {
// Here you can manipulate fetched data and prepare the data source for your table view
print(fetchedData)
// Reload table view and call completion only once
self.tableView.reloadData()
completion(true)
}
}
}
I also agree with other comments that you should rethink the data model for your tableView. A much more appropriate structure would be a 2d array (an array of arrays - first array translates to the table view sections and the inner array objects translate to section items).
Here's an example:
// Table view data source
[
// Section 0
[day0, day1],
// Section 1
[day2, day3],
// Section 2
[day4, day5],
]
Example of usage:
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
}
I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)
I have an array of dictionary with custom object in swift.
Now I am comparing the object for add & update.
The logic is as simple to add the data if not exist and update if any change in dictionary.
User is custom object type:
#objc public class User: NSObject , Mappable
from the getUserID i can able to get userID
The below code is execute in for loop from where i am passing User object.
var peopleList = [User]()
if self.peopleList.count > 0 {
if self.peopleList.contains(where: {$0.getUserID() == users.getUserID()})
{
// check for any update in dist
if let index = self.peopleList.index(of: users)
{
if users.isEqual(self.peopleList[index])
{
print("equal no updates")
}
else
{
print("need to updates objects..")
}
}
//already exist room
}
else
{
self.peopleList.append(users)
}
}
I know it may be related to equatable
so I am using below fuction
func isEqual<T: Equatable>(type: T.Type, a: Any, b: Any) -> Bool? {
guard let a = a as? T, let b = b as? T else { return nil }
return a == b
}
But I am getting index = nil.
Is there any idea or suggestion to solve it.
If any other way to do it efficiently them most welcome.
I think this simplified version should work
if self.peopleList.isEmpty, let user = self.peopleList.first(where: { $0.getUserID() == users.getUserID() }) {
if user == users {
// is equal
} else {
// do update
}
} else {
self.peopleList.append(users)
}
Ok so lets say I have an custom object for vocabulary words, alternate way of being written, and their meaning.
class VocabEntry {
var kanji:String?
var kana:String?
var meaning:String?
}
Then I have an array comprised of these objects. Here's one for example.
let newVocabWord = VocabEntry()
newVocabWord.kanji = "下さい”
newVocabWord.kana = "ください”
newVocabWord.meaning = "please"
Now I have a string of text:
let testString = "すみません、十階のボタンを押して下さい"
How can I compare that string to my array of custom objects (that contain strings) and reference the matches?
I tried.
if vocabArray.contains( { $0.kanji == testString }) {
print("yes")
}
But that trying to match the entire string. If I change testString to "下さい" it works, but that's not what I'm looking for. What I want is for it to say "Here I found 下さい in xx object. Here's the index number."
You can use indexOf() with a predicate to find the index of a
matching entry, and containsString() to search for substrings.
Since the kanji property is optional, you have to check that via
optional binding:
if let index = vocabArray.indexOf({ entry in
if let kanji = entry.kanji {
// check if `testString` contains `kanji`:
return testString.containsString(kanji)
} else {
// `entry.kanji` is `nil`: no match
return false
}
}) {
print("Found at index:", index)
} else {
print("Not found")
}
This can be written more concise as
if let index = vocabArray.indexOf({
$0.kanji.flatMap { testString.containsString($0) } ?? false
}) {
print("Found at index:", index)
} else {
print("Not found")
}
To get the indices of all matching entries, the following would work:
let matchingIndices = vocabArray.enumerate().filter { (idx, entry) in
// filter matching entries
entry.kanji.flatMap { testString.containsString($0) } ?? false
}.map {
// reduce to index
(idx, entry) in idx
}
print("Found at indices:", matchingIndices)
I have this Firebase data:
I want to query the posts data through pagination. Currently my code is converting this JS code to Swift code
let postsRef = self.rootDatabaseReference.child("development/posts")
postsRef.queryOrderedByChild("createdAt").queryStartingAtValue((page - 1) * count).queryLimitedToFirst(UInt(count)).observeSingleEventOfType(.Value, withBlock: { snapshot in
....
})
When accessing, this data page: 1, count: 1. I can get the data for "posts.a" but when I try to access page: 2, count: 1 the returns is still "posts.a"
What am I missing here?
Assuming that you are or will be using childByAutoId() when pushing data to Firebase, you can use queryOrderedByKey() to order your data chronologically. Doc here.
The unique key is based on a timestamp, so list items will automatically be ordered chronologically.
To start on a specific key, you will have to append your query with queryStartingAtValue(_:).
Sample usage:
var count = numberOfItemsPerPage
var query ref.queryOrderedByKey()
if startKey != nil {
query = query.queryStartingAtValue(startKey)
count += 1
}
query.queryLimitedToFirst(UInt(count)).observeSingleEventOfType(.Value, withBlock: { snapshot in
guard var children = snapshot.children.allObjects as? [FIRDataSnapshot] else {
// Handle error
return
}
if startKey != nil && !children.isEmpty {
children.removeFirst()
}
// Do something with children
})
I know I'm a bit late and there's a nice answer by timominous, but I'd like to share the way I've solved this. This is a full example, it isn't only about pagination. This example is in Swift 4 and I've used a nice library named CodableFirebase (you can find it here) to decode the Firebase snapshot values.
Besides those things, remember to use childByAutoId when creating a post and storing that key in postId(or your variable). So, we can use it later on.
Now, the model looks like so...
class FeedsModel: Decodable {
var postId: String!
var authorId: String! //The author of the post
var timestamp: Double = 0.0 //We'll use it sort the posts.
//And other properties like 'likesCount', 'postDescription'...
}
We're going to get the posts in the recent first fashion using this function
class func getFeedsWith(lastKey: String?, completion: #escaping ((Bool, [FeedsModel]?) -> Void)) {
let feedsReference = Database.database().reference().child("YOUR FEEDS' NODE")
let query = (lastKey != nil) ? feedsReference.queryOrderedByKey().queryLimited(toLast: "YOUR NUMBER OF FEEDS PER PAGE" + 1).queryEnding(atValue: lastKey): feedsReference.queryOrderedByKey().queryLimited(toLast: "YOUR NUMBER OF FEEDS PER PAGE")
//Last key would be nil initially(for the first page).
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists(), let value = snapshot.value else {
completion(false, nil)
return
}
do {
let model = try FirebaseDecoder().decode([String: FeedsModel].self, from: value)
//We get the feeds in ['childAddedByAutoId key': model] manner. CodableFirebase decodes the data and we get our models populated.
var feeds = model.map { $0.value }
//Leaving the keys aside to get the array [FeedsModel]
feeds.sort(by: { (P, Q) -> Bool in P.timestamp > Q.timestamp })
//Sorting the values based on the timestamp, following recent first fashion. It is required because we may have lost the chronological order in the last steps.
if lastKey != nil { feeds = Array(feeds.dropFirst()) }
//Need to remove the first element(Only when the lastKey was not nil) because, it would be the same as the last one in the previous page.
completion(true, feeds)
//We get our data sorted and ready here.
} catch let error {
print("Error occured while decoding - \(error.localizedDescription)")
completion(false, nil)
}
}
}
Now, in our viewController, for the initial load, the function calls go like this in viewDidLoad. And the next pages are fetched when the tableView will display cells...
class FeedsViewController: UIViewController {
//MARK: - Properties
#IBOutlet weak var feedsTableView: UITableView!
var dataArray = [FeedsModel]()
var isFetching = Bool()
var previousKey = String()
var hasFetchedLastPage = Bool()
//MARK: - ViewController LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
//Any other stuffs..
self.getFeedsWith(lastKey: nil) //Initial load.
}
//....
func getFeedsWith(lastKey: String?) {
guard !self.isFetching else {
self.previousKey = ""
return
}
self.isFetching = true
FeedsModel.getFeedsWith(lastKey: lastKey) { (status, data) in
self.isFetching = false
guard status, let feeds = data else {
//Handle errors
return
}
if self.dataArray.isEmpty { //It'd be, when it's the first time.
self.dataArray = feeds
self.feedsTableView.reloadSections(IndexSet(integer: 0), with: .fade)
} else {
self.hasFetchedLastPage = feeds.count < "YOUR FEEDS PER PAGE"
//To make sure if we've fetched the last page and we're in no need to call this function anymore.
self.dataArray += feeds
//Appending the next page's feed. As we're getting the feeds in the recent first manner.
self.feedsTableView.reloadData()
}
}
}
//MARK: - TableView Delegate & DataSource
//....
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if self.dataArray.count - 1 == indexPath.row && !self.hasFetchedLastPage {
let lastKey = self.dataArray[indexPath.row].postId
guard lastKey != self.previousKey else { return }
//Getting the feeds with last element's postId. (postId would be the same as a specific node in YourDatabase/Feeds).
self.getFeedsWith(lastKey: lastKey)
self.previousKey = lastKey ?? ""
}
//....
}