How to make alphabetical section with contacts in tableView in swift - swift

I'm new to Swift and I have arrays made with CNContact(familyName, givenName, phoneNumber).
I'd like to make name contacts alphabetical order and group them in sections in order to put them in the "titleForHeaderInSection" as below.
Does anyone know how to group and put it into titleForHeaderInSection??
struct AddressModel {
let nameAndPhone: [AddressContact]
}
struct AddressContact {
let contact: CNContact
}
class AddressViewController: UITableViewController {
var addressArray = [AddressModel]()
private func fetchContacts() {
print("Attempting to fetch contacts today")
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, err) in
if let err = err {
print("Failed to request access:", err)
return
}
if granted {
let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactPhoneNumbersKey] as [Any]
let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.userDefault
do {
var addressContact = [AddressContact]()
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
addressContact.append(AddressContact(contact: contact))
})
let nameAndPhone = AddressModel(nameAndPhone: addressContact)
self.addressArray = [nameAndPhone]
} catch let err {
print("Failed to enumerate contacts:", err)
}
} else {
print("Access denied..")
}
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section)"
}
override func numberOfSections(in tableView: UITableView) -> Int {
return self.addressArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.addressArray[section].nameAndPhone.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let nameAndPhone = addressArray[indexPath.section].nameAndPhone[indexPath.row]
let fullName = nameAndPhone.contact.familyName + nameAndPhone.contact.givenName
cell.textLabel?.text = fullName
return cell
}

Try this
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
// ...
}

For sorting section title try the closure:
sectionTitle = sectionTitle.sorted(by: { $0 < $1 }) // First argument smaller then second argument.

Related

Swift table view and search controller didSelectRowAt index out of range

In Swift, I'm using SearchController and TableView with search API below.
https://developers.giphy.com/docs/api/endpoint#search
api.giphy.com/v1/gifs/search
When I input to SearchController field, TableView normally reloaded(autocompleted search keywords appear). But when I select row of TableView, 'Index out of range' error(in my ViewModel) occurred in some situation and I can't figure out what is cause.
Error occurred line
func searchKeyword(indexPath: Int) -> String? in ViewModel
This is my code.
ViewController
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let count = self.viewModel?.resultsCount() else { return 0 }
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = searchTableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath) as? searchTableViewCell else { return UITableViewCell() }
guard let result = self.viewModel?.selectedKeyword(at: indexPath.row) else { return UITableViewCell() }
cell.configureCell(result: result.name)
return cell
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text?.lowercased() else { return }
self.viewModel?.fetchKeywords(searchKeyword: text)
self.searchTableView.reloadData()
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let nextViewController = self.storyboard?.instantiateViewController(withIdentifier: "NextViewControllerID") as? NextViewController else { return }
guard let result = self.viewModel?.searchKeyword(indexPath: indexPath.row) else { return }
nextViewController.showNextViewController(with: NextViewModel(keyword: result))
self.navigationController?.pushViewController(nextViewController.show, animated: true)
}
}
extension ViewController: UISearchResultsUpdating {
func updateResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text?.lowercased() else { return }
self.viewModel?.fetchKeywords(searchKeyword: text)
self.searchResults.reloadData()
}
}
ViewModel
func fetchKeywords(searchKeyword: String) {
self.useCase.fetchKeywords(searchKeyword: searchKeyword, paginationCount: 5) { error, result in
if let error = error {
self.error = error
}
if let result = result {
self.results = result.searchKeywords
}
}
}
func resultsCount() -> Int {
guard let count = self.results?.count else { return 0 }
return count
}
// Fatal error: Index out of range
func searchKeyword(indexPath: Int) -> String? {
guard let results = results else { return nil }
return results[indexPath].name
}
func selectedKeyword(at indexPath: Int) -> Keyword? {
guard let results = self.results else { return nil }
return results[indexPath]
}

Problem assigning data to tableview - Swift

Although I load the data into the gifsa string array in the function, I cannot see the gifsa data in the tableView. gifsa data does not appear in tableView. The data is loading data in the veriCek() function. However, it does not load data into the imageView found in cell. What is the problem?
class NewDegisimController: UITableViewController {
var gifsa: [String] = []
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gifsa.count
}
override func viewDidLoad() {
super.viewDidLoad()
veriCek()
}
func veriCek(){
let client = SQLClient.sharedInstance()!
client.connect("...", username: "...", password: "...", database: "...") { success in
client.execute("SELECT ... FROM ...", completion: { (_ results: ([Any]?)) in
for table in results as! [[[String:AnyObject]]] {
for row in table {
for (_, value) in row {
if let intVal = value as? String {
self.gifsa.append(String(intVal))
}} }}
client.disconnect()
}) }
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
let model = models[indexPath.row]
do {
print("gifsaas",self.gifsa)
let url = URL(string: self.gifsa[indexPath.row])
let data = try Data(contentsOf: url!)
cell.imageView?.image = UIImage(data: data)
cell.textLabel?.text = model.title
}
catch{
print(error)
}
return cell
}
You need to reload
for table in results as! [[[String:AnyObject]]] {
for row in table {
for (_, value) in row {
if let intVal = value as? String {
self.gifsa.append(String(intVal))
}} }}
DispatchQueue.main.async {
self.tableView.reloadData()
}
You just need to call
self.tableView.reload()
in your veriCek() function and you are all set

TableView - “Thread 1: Fatal error: Index out of range in Swift”

I'm pulling data from the database, I'm having no problems with data, but I'm having trouble transferring this data to the text in the Cell. I think I'm having a problem with the numberOfRowsInSection count. I want to add as much data as the sum of the two data, but I'm having trouble this way.
class ChatRoomViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var bilgiCevap = [String]()
var bilgiKullanıcı = [String]()
override func viewDidLoad() {
super.viewDidLoad()
FCevap()
chatTableView.delegate = self
chatTableView.dataSource = self
func FCevap(){
let client = SQLClient.sharedInstance()!
client.connect("...", username: "...", password: "...", database: "...") { success in
client.execute("SELECT ... FROM ... WHERE . LIKE '\(self.form_no)' AND ... LIKE '0'", completion: { (_ results: ([Any]?)) in
for table in results as! [[[String:AnyObject]]] {
for row in table {
for (_, value) in row {
if let intVal = value as? String {
self.bilgiKullanıcı.append(String(intVal))
}} }}
DispatchQueue.main.async {
self.Kullanici()
self.chatTableView.reloadData()}
client.disconnect() }) }
}
func Kullanıcı(){
let client = SQLClient.sharedInstance()!
client.connect("...", username: "...", password: "...", database: "...") { success in
client.execute("SELECT ... FROM ... WHERE ... LIKE '\(self.form_no)' AND ... LIKE '1'", completion: { (_ results: ([Any]?)) in
for table in results as! [[[String:AnyObject]]] {
for row in table {
for (_, value) in row {
if let intVal = value as? String {
self.bilgiCevap.append(String(intVal))
}} }}
DispatchQueue.main.async {self.chatTableView.reloadData()}
client.disconnect() }) }
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatTableView.dequeueReusableCell(withIdentifier: "chatCell") as! ChatCell
let messageGıden = self.bilgiKullanıcı[indexPath.row]
cell.chatTextView.text = messageGıden
cell.usernameLabel.text = "..."
cell.setBubbleType(type: .incoming)
let messageGelen = self.bilgiCevap[indexPath.row]
cell.chatTextView2.text = messageGelen
cell.userNameLabel2.text = "Kullanıcı"
cell.setBubbleType2(type: .outgoing)
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return bilgiCevap.count + bilgiKullanici.count
}
}
if you have data sources you need to take two Sections in number of section delegate.
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
And in numberOfRows section
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return dataSource1.count
} else {
return dataSource2.count
}
}
and then inside cellFor rowAt delegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let data = dataSource1[indexPath.row]
} else {
let data = dataSource2[indexPath.row]
}
}

Add section after 7 cell created in TableView - Swift

I have 2 arrays, one that is containing the sections and another one that is containing the elements for populate the cells of my TableView.
The question is: is it possible to create multiple sections with title after 7 cells?
For example, the array contains 14 elements and sections array 2 elements. I wish that at the beginning will appear "Section 1" then first 7 elements, then "Section 2" then the rest of the elements.
Thank you!
import UIKit
class ChallengesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var titleArray = [""]
var weekSection = ["1","2"]
override func viewDidLoad() {
super.viewDidLoad()
> let url = URL(string:"https://website.com/file.txt")!
> URLCache.shared.removeAllCachedResponses()
> let task = URLSession.shared.dataTask(with:url) { (data, response, error) in
> if error != nil {
> print(error!)
> }
> else {
> if let textFile = String(data: data!, encoding: .utf8) {
> DispatchQueue.main.async {
> self.titleArray = textFile.components(separatedBy: "\n")
> self.tableView.reloadData()
> print(self.titleArray)
> }
> }
> }
> }
> task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titleArray.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return weekSection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "challengeCell", for: indexPath) as! ChallengeTableViewCell
cell.labelTitle.text = titleArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch (section) {
case 0:
return "Case 0"
case 1:
return "Case 1"
default:
return "Default"
}
}
Update numberOfRowsInSection and cellForRowAt methods like this
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Case \(section)"
}
func numberOfSections(in tableView: UITableView) -> Int {
let lastSection = titleArray.count % 7 == 0 ? 0 : 1
return (titleArray.count/7) + lastSection
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = titleArray.count - (7*section)
return min(7,count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "challengeCell", for: indexPath) as! ChallengeTableViewCell
let rowIndex = (indexPath.section*7)+indexPath.row
cell.labelTitle.text = titleArray[rowIndex]
return cell
}
after the edit of the question, this is one of the best answers I think
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
usage:
#IBOutlet weak var tableView: UITableView!
var titleArray: [[String]] = [[]]
var sectionsCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string:"https://website.com/file.txt")!
URLCache.shared.removeAllCachedResponses()
let task = URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
print(error!)
}
else {
if let textFile = String(data: data!, encoding: .utf8) {
DispatchQueue.main.async {
let myArray = textFile.components(separatedBy: "\n")
self.sectionsCount = myArray.chunked(into: 7).count
self.tableView.reloadData()
print(self.titleArray)
}
}
}
}
task.resume()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsCount
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titleArray[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "challengeCell", for: indexPath) as! ChallengeTableViewCell
let sectionArray = titleArray[indexPath.section]
cell.labelTitle.text = sectionArray[indexPath.row]
return cell
}

swift, numberOfRowsInSection and cellForRowAt functions don't return values

I have an issue, numberOfRowsInSection and cellForRowAt functions don't return values. I need to return name and price from coincap.io/front API into tableView. I tried to change return values to integer and it works fine, but anyway I can't return another values.
Here is code:
import UIKit
import Alamofire
import SwiftyJSON
class Currency : NSObject {
var name : String!
var price : Double!
init(name : String, price : Double) {
self.name = name
self.price = price
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let DATA_URL : String = "http://coincap.io/front"
var currencies = [Currency]()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getData(url: DATA_URL)
}
func getData(url: String) {
Alamofire.request(url, method: .get).responseJSON { response in
if response.result.isSuccess {
print("Success! Got the data")
let dataJSON : JSON = JSON(response.result.value!)
// print(dataJSON)
self.updateData(json: dataJSON)
} else {
print("Error \(String(describing: response.result.error))")
}
}
}
func updateData(json: JSON) {
for (_, current) in json{
currencies.append(Currency(name: current["long"].stringValue, price: current["price"].doubleValue))
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return NSInteger(counter)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCell", for: indexPath)
let current = currencies[indexPath.row]
cell.textLabel?.text = current.name
cell.detailTextLabel?.text = "\(current.price!)"
return cell
}
}
You have to call reload
func getData(url: String) {
Alamofire.request(url, method: .get).responseJSON { response in
if response.result.isSuccess {
print("Success! Got the data")
let dataJSON : JSON = JSON(response.result.value!)
// print(dataJSON)
self.updateData(json: dataJSON)
DispatchQueue.main.async {self.tableView.reloadData()}
} else {
print("Error \(String(describing: response.result.error))")
}
}
}
Also replace
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return NSInteger(counter)
}
with
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currencies.count
}