How to search data in tableview? - ios

I have data in local file, I have loaded them into the tableView, and now I want to search/filter via search bar.
I have tried some solutions I've found on the Internet, but I don't know how to access specific data in my structure.
Here is part of the structure:
struct Model: Codable {
var data: [Description]
}
struct Description: Codable {
var id: Int
var name: String
var address: String
var city: String
var description: String
var countryId: Int
var webSite: String
var status: Int
var accountType: Int
var addDate: String
var placeGroupId: Int
var longitude: Double
var latitude: Double
var distance: Double
var working: Bool
var promotion: String
var repertoire: Repertoire
var workingHour: WorkingHour
var country: Country
var reviewNum: Int
var score: Double
var placeImgUrl: String
}
var model: Model?
and here is some code from my ViewController:
var filteredObjects = [Description]()
let searchController = UISearchController(searchResultsController: nil)
func searchBarIsEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
<#code#>
}
}
I have variable model, which is type of struct Model. Structure Model have a variable data which is type of struct Description. And finally, in structure Description I have variable name. My tableView cells shows names of models (model?.data[indexPathrow.row].name). Now, I need to search by names shown in tableView cells. I hope I explained it clearly. Thank You very much!

Assuming objects is also [Description] and represents the data source array
func updateSearchResults(for searchController : UISearchController)
{
let searchString = searchController.searchBar.text!
if searchString.isEmpty {
filteredObjects.removeAll()
} else {
filteredObjects = objects.filter{ $0.name.range(of: searchString, options: .caseInsensitive) != nil }
}
tableView.reloadData()
}
In the table view data source methods display objects or filteredObjects depending on searchController.isActive

Related

How to display all items from a Set

My app consists on an image of various foods, in which the user taps the image and adds this food into a Set<Food>.
I want to show all items from this Set inside the class called Favorites, as a: Text("You like: \(favorites.comidas)") but I can't manage to make it work
class Favorites: ObservableObject {
var foods: Set<Food>
}
class Favorites: ObservableObject {
var foods: Set<Food>
init() {
// load the saved data
foods = []
}
func contains(_ food: Food) -> Bool {
foods.contains(food)
}
func add(_ food: Food) {
objectWillChange.send()
foods.insert(food)
save()
}
func delete(_ food: Food) {
objectWillChange.send()
foods.remove(food)
save()
}
}
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
#EnvironmentObject var favorites: Favorites
let food: Food
var body: Some View {
Image(food.foodImage[0])
.onTapGesture {
if favorites.contains(food) {
favorites.delete(food)
} else {
favorites.add(food)
}
}
}
You haven't shown your Food structure, but I will assume it has a property, name.
ListFormatter is your friend with a task like this. Its string(from:[]) function takes an array and returns it in a nicely formatted list. You can use map to get an array of name strings from your set.
For the input array ["pizza","tacos","chocolate"] it will give "pizza, tacos and chocolate"
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name})
return favourites ?? ""
}
Then you can use this function in a Text view:
Text("You like \(self.favoriteList)")
Note that a Set is unordered, so it might be nice to sort the array so that you get a consistent, alphabetical order:
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name}.sorted())
return favourites ?? ""
}
Thanks to a tip from Leo Dabus in the comments, in Xcode 13 and later you can just use .formatted -
var favoriteList: String {
return self.favorites.foods.map{$0.name}.sorted().formatted() ?? ""
}

fill in UITableViewCell with JSONdata

I am trying to display some json data inside my tableView cell, and parsed the json data.
But
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contents.count
}
returning 0
and as a result, I cannot display the data inside the UITableViewCell eventhough I have all my JSON data have been parsed and ready to be displayed.. .
how to fix it?
my response Model class and the rest of implementation are as follows:
Model classes for json response
// MARK: - TradingPairElement
class TradingPair: Entity {
var id: Int?
//var name: String?
var quoteAsset: QuoteAsset?
}
enum QuoteAsset: String, Codable {
case btc = "BTC"
case krw = "KRW"
}
// MARK: - TickerByPair
class TickerByPair: Entity {
var ask: Int?
//var price: Double?
//var volume: Double?
var askVolume: Double?
var bid: Int?
var bidVolume: Double?
var time: String?
}
And a wrapper class for the above two class 's contents:
class Entity: Codable {
var name: String?
var price: Double?
var volume: Double?
}
and here is how i am getting the data from the api and assigning to my self variables:
func APIcall() {
ServerCommunicator.getPairs().done{ response -> Void in
for keyPathParam in response {
self.keyPathParam = keyPathParam.name
}
ServerCommunicator.getPair(with: self.keyPathParam).done{ ticker -> Void in
for data in self.contents {
data.name = ticker.name
data.price = ticker.price
data.volume = ticker.volume
self.contents.append(data)
}
}.catch{(err) in
print(err)
}
}.catch{(error) in
print(error)
}
}
First of all if the API sends always all keys declare the properties non-optional and as constants and most likely you don't need a class and conformance to Encodable
struct Entity: Decodable {
let name: String
let price: Double
let volume: Double
}
After getting the data from the server you have to create new instances of Entity and assign them to the data source array. Further you need DispatchGroup to handle the loop and reload the table view after the last entity has been created.
If you want to overwrite self.contents with the received data uncomment the removeAll line
func APIcall() {
ServerCommunicator.getPairs().done{ response in
// self.contents.removeAll()
let group = DispatchGroup()
for keyPathParam in response {
group.enter()
ServerCommunicator.getPair(with: keyPathParam.name).done{ ticker in
let entity = Entity(name: ticker.name, price: ticker.price, volume: ticker.volume)
self.contents.append(entity)
group.leave()
}.catch{(err) in
print(err)
group.leave()
}
}
group.notify(queue: .main) {
self.tableView.reloadData()
}
}.catch{(error) in
print(error)
}
}

Set Table Section Titles from Realm Query

I've searched for the answer to this (I'm sure it's there somewhere) but can't find it.
I am trying to populate a UITableView's section headers from a Realm database, where the section title is in a related class.
My Realm classes:
class Person: Object {
#objc dynamic var personId = UUID().uuidString
#objc dynamic var firstName: String = ""
#objc dynamic var surname: String = ""
#objc dynamic var mobileNumber: Int = 0
#objc dynamic var password: String = ""
override static func primaryKey() -> String? {
return "personId"
}
}
class Group: Object {
#objc dynamic var groupId = UUID().uuidString
#objc dynamic var person: Person?
#objc dynamic var groupName: String = ""
let groupContent = List<String>()
override static func primaryKey() -> String? {
return "groupId"
}
}
I want to retrieve the groupName results for the current user and use them as table section headers. The number of groupNames is dynamic for each user.
My current code, which doesn't work at all is:
func getGroupNames() {
let mobileNumber = UserDefaults.standard.integer(forKey: "mobileNumber")
let personResult = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber)
let groupNames = realm.objects(Group.self).filter("person == %#", personResult.self.first)
return (groupNames)
}
I can't get groupNames to be useful as section headers.
Help will be appreciated!
Thanks.
UPDATE
I now have:
func getGroupNames() -> [String] {
let mobileNumberInt = mobileNumber
let groupNames = realm.objects(Group.self).filter("person.mobileNumber == %#", mobileNumberInt).map({$0.groupName})
return Array(groupNames)
}
This returns ["Group Name 1", "Group Name 2"] twice (no matter how many objects are in the results). Why twice, and now how do I get these into my section headers? I've tried:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> [String] {
return getGroupNames()
}
The quantity of sections works, but the headers are not showing.
Assuming you already know the mobile number of a person, you can use following code to fetch groups a person belongs to.
func getGroupNames() -> [Group]? {
let realm = try Realm()
let mobileNumber = UserDefaults.standard.integer(forKey: "mobileNumber")
let groupNames = realm.objects(Group.self).filter("person.mobileNumber == %#", mobileNumber)
return Array(groupNames)
}

How to save position of reordered cells in Collection View after I restart the app? Swift

When I am trying to reorder position of cells in UICollectionView using these two methods:
var teams: [Team]?
override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
if let temp = teams?[sourceIndexPath.item] {
teams?[sourceIndexPath.item] = (teams?[destinationIndexPath.item])!
teams?[destinationIndexPath.item] = temp
}
print("Starting Index: \(sourceIndexPath.item)")
print("Ending Index: \(destinationIndexPath.item)")
}
It works fine however after restarting my app I want to save position of reordered cells.
Which approach could you recommend me?
Additional info:
The Array of "teams" is storing objects of class Team:
class Team: NSObject {
var id: String?
var name: String?
var logo: String?
var players: [Player]?
}
class Player: NSObject {
var alias: String?
var name: String?
var age: String?
var country: String?
var imageName: String?
var info: Info?
}
class Info: NSObject {
var screenshots: [String]?
var bio: String?
var gear: Gear?
var povs: [String]?
var cfg: Config?
}
class Gear: NSObject {
var monitor: String?
var mouse: String?
var mousepad: String?
var keyboard: String?
var headset: String?
}
class Config: NSObject {
var mouseSettings: [String]?
var monitorSettings: [String]?
var crosshaircfg: [String]?
}
Thank you in advance for your help!
I'd use UserDefaults for this.
I stored the position as an integer in the userDefaults and used the item name as the key.
How to store the positions
func saveReorderedArray() {
for (index, item) in yourArray.enumerated() {
let position = index + 1
UserDefaults.standard.set(position, forKey: item.name)
}
}
And upon application launch I called reorderArray function to grab the positions and stored it in a array of dictionaries using the item names as the keys.
How to retreive the positions
func reorderArray() {
var items: [[String: Int]] = []
// Get the positions
for item in yourArray {
var position = UserDefaults.standard.integer(forKey: item.name)
// If a new item is added, set position to 999
if position == 0 {
position = 999
}
items.append([itemName : position])
}
for item in items {
// Get position from dictionary
let position = Array(item.values)[0]
let itemName = Array(item.keys)[0]
// Get index from yourArray
let index = yourArray.index { (item) -> Bool in
item.name == itemName
}
// Arrange the correct positions for the cells
if let i = index {
let m = yourArray.remove(at: i)
// Append to last position of the array
if position == 999 {
yourArray.append(m)
}
else {
yourArray.insert(m, at: position - 1) // Insert at the specific position
}
}
}
}
I hope it helped!

How to search through array properties in Model

I have to make a Contacts-like app. I have a model object. Please notice the 2 properties phoneNumbers & addresses are Arrays
class Contact: NSObject {
var name: String?
var companyName: String?
var phoneNumbers: [String]?
var addresses: [String]?
.. // Custom init methods etc
}
Now I have to populate these and search them using search View Controller.
I followed the simple way, implemented a searchViewController in TableViewController. Much like here: https://www.raywenderlich.com/113772/uisearchcontroller-tutorial
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredContacts.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %# OR companyName CONTAINS[c] %#", searchController.searchBar.text!, searchController.searchBar.text!, searchController.searchBar.text!)
let array = (contacts as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredContacts = array as! [Contact]
tableView.reloadData()
}
My problem is how to search throught the array of phone numbers and addresses? The above code works good searching through non-array properties, but how to search through the arrays?
Here is code you paste in a playground to test searching within properties that are arrays. I kept the search functions simple for clarity, but the code can be shortened using filters and closures.
import UIKit
class Contact {
var name: String?
var companyName: String?
var phoneNumbers: [String]?
var addresses: [String]?
init (name: String, companyName: String, phoneNumbers: [String], addresses: [String]) {
self.name = name
self.companyName = companyName
self.phoneNumbers = phoneNumbers
self.addresses = addresses
}
}
var contacts: [Contact] =
[(Contact.init(name: "Tom Jones", companyName: "Jones", phoneNumbers: ["111-1111", "111-1112", "111-1113"], addresses: ["Hollywood", "New York"])),
(Contact.init(name: "Aretha Franklin", companyName: "Franklin", phoneNumbers: ["111-1114", "111-1115", "111-1116"], addresses: ["Detroit", "Hollywood"])),
(Contact.init(name: "Axel Rose", companyName: "Rose", phoneNumbers: ["111-1117", "111-11128", "111-1111"], addresses: ["London", "New York"])) ]
let phoneNumberSearchString = "111-1111"
func searchByPhoneNumber (searchNumber: String, inDataSet contacts: [Contact]) -> [Contact] {
var searchResult: [Contact] = []
for contact in contacts {
for phoneNumber in contact.phoneNumbers! {
if phoneNumber == searchNumber {
searchResult.append(contact)
}
}
}
return searchResult
}
func searchByAddress (searchAddress: String, inDataSet contacts: [Contact]) -> [Contact] {
var searchResult: [Contact] = []
for contact in contacts {
for address in contact.addresses! {
if address == searchAddress {
searchResult.append(contact)
}
}
}
return searchResult
}
let foundContacts = searchByPhoneNumber("111-1111", inDataSet: contacts)
for contact in foundContacts {
print(contact.name!)
}
let foundContacts2 = searchByAddress("Hollywood", inDataSet: contacts)
for contact in foundContacts2 {
print(contact.name!)
}
/* Print results
Tom Jones
Axel Rose
Tom Jones
Aretha Franklin
*/

Resources