UITableView freezes till loop complete - ios

I am using this code to search text in Database tables:
func runSearch(_ pattern: String) {
for book in selectedBooks {
let path = Bundle.main.path(forResource: "library", ofType: "sqlite")!
let sqlState = "select * from \(book.name)"
guard
let db = FMDatabase(path: path), db.open(),
let result = db.executeQuery(sqlState, withArgumentsIn: [])
else { return }
while result.next() {
RunLoop.current.run(mode: .default, before: .distantPast)
let pageID = result.long(forColumn: "Id")
let pageText = result.string(forColumn: "page_text") ?? ""
if isMatches(regex: pattern, in: pageText) {
dataSource.append(dbItem(id: pageID, text: pageText))
resultsLabel.text = "Results count: " + dataSource.count.description
let indexPath = IndexPath(row: dataSource.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
}
func isMatches(regex: String, in str: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex)
let matches = regex.matches(in: str, range: NSRange(location: 0, length: str.count))
return matches.count != 0
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return false
}
I have over 40 books to search through. And the search is done fine, but the "UITableView" freezes until the search is complete. I want to allow the user to scroll through this table and choose what result he wants to display while searching.
Can anyone help me with this, please?

You can make an asynchronous function where you do all the work and after the function ends call tableView.reloadData. Also, you can not insert rows after every loop but when the whole loop ends and you have the right data in your data source then reload data to your table view to refresh it with the new data.

Related

How can you ensure that cells are not duplicated when the device is overwhelmed by code from the previous page?

I have a problem that only occurs when page A has to run a massive amount of code and the user goes to page B before all the A page code is finished. In these instances, sometimes cells get duplicated(ie, say page B must be : User H in top, user F below him. Instead there are two Hs followed by two Fs below them).
Below is the relevant code of page B, but I am fairly certain the problem does not lie there. Why?: Because I changed the array that gets displayed [H,F] to a set, so according to the code, there should never be an instance like [H,H,F,F]
///Part1: code that gets called from viewdidLoad
var peppi = [Usery]()
func printPersonInfo(uid: String) {
self.handleA = thisUser.observe(DataEventType.value, with: { snapshot in
...
myPeopleRef44.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { [self] snapshot in
let uniqueArray = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray))
for person in peopleArray where uid == person.value as? String {
...
func decode(autoId: String) -> TimeInterval {
}
return TimeInterval(exactly: timestamp)!
}
...
if Calendar.current.isDateInToday(date){
let p = Usery(...)
peppi.append(p)
}
}
DispatchQueue.main.async {
self.peppi.sort { ($0.self.time1 ?? 0) > ($1.self.time1 ?? 0)
}
print(self.peppi, "lo")
self.table.reloadData()
}
})
})
}
/// Part: 2 In viewDidLoad, code that calls the function printPersonInfo
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { snapshot in
func decode(autoId: String) -> TimeInterval {
..
return …
}
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
self.dg.leave()
self.dg.notify(queue: .main) {
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid)
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})

QueryEndingAtValue function does not work correctly in Swift firebase

I have been implementing code that is to enable paging scroll to fetch data by a certain amount of data from firebase database.
Firstly, then error says
Terminating app due to uncaught exception 'InvalidQueryParameter',
reason: 'Can't use queryEndingAtValue: with other types than string in
combination with queryOrderedByKey'
The below here is the actual code that produced the above error
static func fetchPostsWith(last_key: String?, completion: #escaping (([PostModel]?) -> Void)) {
var posts = [PostModel]()
let count = 2
let ref = Database.database().reference().child(PATH.all_posts)
let this_key = UInt(count + 1)
let that_key = UInt(count)
let this = ref.queryOrderedByKey().queryEnding(atValue: last_key).queryLimited(toLast: this_key)
let that = ref.queryOrderedByKey().queryLimited(toLast: that_key)
let query = (last_key != nil) ? this : that
query.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
for snap in snapshot.children {
if let d = snap as? DataSnapshot {
let post = PostModel(snapshot: d)
print(post.key ?? "")
posts.append(post)
}
}
posts.sort { $0.date! > $1.date! }
posts = Array(posts.dropFirst())
completion(posts)
} else {
completion(nil)
}
}
}
What it tries to do is to fetch a path where all posts are stored by auto id. But the error keeps coming out so I do not know what is wrong. Do you have any idea?
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// index is the last and last fetched then
print(self.feeds.count - 1 == indexPath.row, "index ", self.hasFetchedLastPage, "has fetched last")
if self.feeds.count - 1 == indexPath.row {
let lastKey = self.feeds[indexPath.row].key
if lastKey != self.previousKey {
self.getFeeds(lastKey: lastKey)
} else {
self.previousKey = lastKey ?? ""
}
}
print("Cell Display Number", indexPath.row)
}
func getFeeds(lastKey: String?) {
print(self.isFetching, "is fetching")
guard !self.isFetching else {
self.previousKey = ""
return
}
self.isFetching = true
print(self.isFetching, "is fetching")
FirebaseModel.fetchPostsWith(last_key: lastKey) { ( d ) in
self.isFetching = false
if let data = d {
if self.feeds.isEmpty { //It'd be, when it's the first time.
self.feeds.append(contentsOf: data)
self.tableView.reloadData()
print("Initial Load", lastKey ?? "no key")
} else {
self.hasFetchedLastPage = self.feeds.count < 2
self.feeds.append(contentsOf: data)
self.tableView.reloadData()
print("Reloaded", lastKey ?? "no key")
}
}
}
}
I want to implement a paging enabled tableView. If you can help this code to be working, it is very appreciated.
You're converting your last_key to a number, while keys are always strings. The error message tells you that the two types are not compatible. This means you must convert your number back to a string in your code, before passing it to the query:
let this = ref.queryOrderedByKey().queryEnding(atValue: last_key).queryLimited(toLast: String(this_key))
let that = ref.queryOrderedByKey().queryLimited(toLast: String(that_key))

Swift : How to sort alphabetically but with letters first

i have a Realm results list:
subsList = RealmDB.objects(Downloads).filter("isActive = true").sorted("name", ascending: true)
And it display the results ordered, but the first results are "(item 1)" or 1-item, then comes A-item, B-item etc...
How can i sort it in a ways that A-item, B-item etc.. come first and (item 1), 1-item display at the end?
note: for those who don't know realm, it can take a NSPredicate for sorting
Thanks guys
EDIT:
Following the comments, I'm getting
-chloro
(+-)-1,2,3-octa
(amino)
1-propanol
acetone
benzin
dinoterb
TNT
And i need
acetone
benzin
dinoterb
TNT
-chloro
(+-)-1,2,3-octa
(amino)
1-propanol
This is just an example, fix the code to make it look better.
var arr = ["-chloro", "(+-)-1,2,3-octa", "(amino)", "1-propanol", "acetone", "benzin","dinoterb","TNT"]
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
arr.sort { (s1, s2) -> Bool in
let matched = matches(for: "[0-9,\\(,\\-]", in: s1)
let matched2 = matches(for: "[0-9\\(,\\-]", in: s2)
if matched.isEmpty && matched2.isEmpty {
return s1.lowercased() < s2.lowercased()
} else if matched.isEmpty && !matched2.isEmpty {
return true
} else if !matched.isEmpty && matched2.isEmpty {
return false
} else {
return matched.first! < matched2.first!
}
}
result -> ["acetone", "benzin", "dinoterb", "TNT", "(+-)-1,2,3-octa", "(amino)", "-chloro", "1-propanol"]

loop in a loop not working

I'm working on an app for school project.
After accessing the user's contacts, I want to loop through and show only the contacts who are also users of the app. *Their username is their mobile number.
below are 3 functions.
the first one getAppUsers() works fine.
the third one getDisplayedUser() does not work. and i wonder why
the second one getUserContacts() works. but it is only there to check which part of my loop isn't working. :/
so apparently my loop in a loop has something wrong which i can't figure out (it didn't even get to the "you're HERE"). please help me out. THANKS!
var appUsers = [String]()
var contactStore = CNContactStore()
var userContacts = [CNContact]()
var displayedContacts = [name: phoneNumber]()
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
}
}
}
func getUserContacts() {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you got here")
print(d)
}
}
func getDisplayedUser() {
for a in appUsers {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you're HERE")
print(d)
if a == d {
print("FOUND IT")
print(b.givenName + " " + b.familyName)
}
}
}
}
The getDisplayedUser should be call after the loop finished in in getAppUsers because it is executing in asynchronous mode. I added the row after finished loop below
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
// Call it here please ..
self.getDisplayedUser()
}
}
}

How to delay a return call in Swift

Hello guys I am currently working on a program that holds a list of books in a UITableView. As you know, the TableView takes two methods, one with cellForRowAtIndexPath and the one I will be talking about today, numberOfRowsInSection. So the problem I am having is that I access my database to get the number of books that are currently in the database in order to return how many indices I will need in the array of Book stucts. So I have two groups, buy and sell, that may or may not have any books in them.
Anyway, I populate my array (it's empty to start with) and then I return the books.count as the numberOfRowsInSection. The problem is that I am consistently returning 0 seeing as the array gets populated after the return is executed.
Below is my code.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
populateArray()
print("books.count: ",books.count)
return books.count // KEEPS RETURNING 0 BC IT HASN'T POPULATED YET *ARRRRRRGH*
}
func populateArray(){
print("started looking")
var indices = 0
if divider.selectedSegmentIndex == 0{
ref.child(school).observeEventType(.Value, withBlock: { (snapshot) in
let numSelling = snapshot.value!["numSelling"] as! Int // gets numSelling
if numSelling > 0 {
self.noEntries = false
print("numSelling: ", numSelling) //see console
indices = numSelling
}else{
self.noEntries = true
indices = 1
print("No Values Selling")
}
}) { (error) in
print(error.localizedDescription)
}
}else{
ref.child(school).observeEventType(.Value, withBlock: { (snapshot) in
let numBuying = snapshot.value!["numBuying"] as! Int // gets numBuying
if numBuying > 0 {
self.noEntries = false
print("numBuying: ", numBuying) //see console
indices = numBuying
}else{
self.noEntries = true
indices = 1
}
}) { (error) in
print(error.localizedDescription)
}
}
delay(0.5){
print("ind: ", indices) // printing correctly
if(self.noEntries){ // just add one book to get the indices to be 1
self.books.append(Book(isbn: "", title: "", author: "", edition: "", price: "", uid: ""))
return
}
if self.divider.selectedSegmentIndex == 0{
self.ref.child(self.school).child("selling").observeEventType(.Value, withBlock: { (snapshot) in
let booksJSON = snapshot.value! as! NSArray
for bookJSON in booksJSON { // add the book to the array
let tempAuthor = bookJSON["authors"] as! String
let tempTitle = bookJSON["title"] as! String
let tempEdition = bookJSON["edition"] as! String
let tempPrice = bookJSON["price"] as! String
let tempISBN = bookJSON["isbn"] as! String
let tempUID = bookJSON["uid"] as! String
self.books.append(Book(isbn: tempISBN, title: tempTitle, author: tempAuthor, edition: tempEdition, price: tempPrice, uid: tempUID))
}
}) { (error) in
print(error.localizedDescription)
}
}else if self.divider.selectedSegmentIndex == 1{
self.ref.child(self.school).child("buying").observeEventType(.Value, withBlock: { (snapshot) in
let booksJSON = snapshot.value! as! NSArray
for bookJSON in booksJSON { // add the book to the array
let tempAuthor = bookJSON["authors"] as! String
let tempTitle = bookJSON["title"] as! String
let tempEdition = bookJSON["edition"] as! String
let tempPrice = bookJSON["price"] as! String
let tempISBN = bookJSON["isbn"] as! String
let tempUID = bookJSON["uid"] as! String
self.books.append(Book(isbn: tempISBN, title: tempTitle, author: tempAuthor, edition: tempEdition, price: tempPrice, uid: tempUID))
}
}) { (error) in
print(error.localizedDescription)
}
}
}
}
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Keep in mind that I cannot make a callback in this method because it is automatically called by the program when the view is loaded.
Also, the delay segments are in efforts to stop the same thing from happening. Problem is that I cannot put the delay around the return because it thinks I want to return an Int for the delay block.
Console:
started looking
books.count: 0
started looking
books.count: 0
started looking
books.count: 0
started looking
books.count: 0
numSelling: 6
numSelling: 6
numSelling: 6
numSelling: 6
ind: 6
ind: 6
ind: 6
ind: 6
As you can see it is returning 0 before it even gets to the numSelling value from the database.
Thank you so much for your help and have a great day!
You cannot delay returning to a method once it has been called, but you can ask the table view to call the data source methods again.
The easiest solution would be to call reloadData() on your table view once your data has been populated (i.e., at the end of your populateArray() method). I would probably also move the call to populateArray() somewhere else (perhaps viewDidLoad(), if that's appropriate).
I would:
Move the "retrieve titles being bought/sold" out of the delay method. Call that from within the respective observeEventType of numSelling/numBuying. Get rid of the delay.
As Charles says, don't worry that the values are empty when viewDidLoad finishes. Just have the routine call tableView.reloadData() when it's done.
Assuming your UI is showing both titles being bought and sold at the same time (or that you're jumping between them quickly and don't want to wait for the data to be retrieved), your routine might want to go ahead and retrieve both arrays and only call reloadData when both are done. This means that you might need two arrays in your model, one for booksSelling and one for booksBuying.

Resources