Making table view section expand swift - ios

I am following this tutorial for expanding and collapsing my table view section. As this demo is done in swift 2.2 I have made all the changes according to swift 3.0 . I am stuck at the below function at if condition(currentSectionCells[row]["isVisible"]) which gives me error as "Type 'NSFastEnumerationIterator.Element' (aka 'Any' has no subscript members)'".
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors {
var visibleRows = [Int]()
for row in 0...((currentSectionCells as! [[String: AnyObject]]).count - 1) {
if currentSectionCells[row]["isVisible"] as! Bool == true {
visibleRows.append(row)
}
}
visibleRowsPerSection.append(visibleRows)
}
}
I have tried type casting it as below
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors {
var visibleRows = [Int]()
for row in 0...((((currentSectionCells) as? NSMutableArray)?.count)! - 1) {
let temp = [currentSectionCells][row] as? NSMutableDictionary
let temp2 = temp?["isVisible"] as! Bool
if temp2 == true {
visibleRows.append(row)
}
}
visibleRowsPerSection.append(visibleRows)
}
}
But this gives me a crash at runtime on this line "let temp2 = temp?["isVisible"] as! Bool"
Crash says "EXC_BAD_INSTRUCTION" and the temp shows as nil.
Please help guys. TIA
Table View Delegate and Data Source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if cellDescriptors != nil {
return cellDescriptors.count
}
else {
return 0
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return visibleRowsPerSection[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath: indexPath as NSIndexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
if currentCellDescriptor["cellIdentifier"] as! String == "sectionCellIdentifier" {
if let primaryTitle = currentCellDescriptor["secondaryTitle"]
{
cell.sectionTitleLabel.text = primaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "shortAnswerCell" {
cell.questionTitle.text = currentCellDescriptor["primaryTitle"] as? String
cell.questionTextView.text = currentCellDescriptor["secondaryTitle"] as? String
cell.answerTextView.text = currentCellDescriptor["answerTitle"] as? String
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexOfTappedRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let temp = cellDescriptors[indexPath.section] as? NSArray
let temp2 = temp?[indexOfTappedRow ] as? NSDictionary
let temp3 = temp2?["isExpandable"] as! Bool
if temp3 == true {
var shouldExpandAndShowSubRows = false
if temp3 == false {
// In this case the cell should expand.
shouldExpandAndShowSubRows = true
}
temp2?.setValue(shouldExpandAndShowSubRows, forKey: "isExpanded")
for i in (indexOfTappedRow + 1)...(indexOfTappedRow + (temp2?["additionalRows"] as! Int)) {
(temp![i] as AnyObject).setValue(shouldExpandAndShowSubRows, forKey: "isVisible")
}
}
getIndicesOfVisibleRows()
tblExpandable.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: UITableViewRowAnimation.fade)
}

I worked on that tutorial as well and completed it successfully in swift3.Your solution is given below modify accordingly.
class yourClass: UIViewController
{
#IBOutlet weak var profileTableView: UITableView!
internal var visibleRowsPerSection = [[Int]]()
internal var cellDescriptors: NSMutableArray!
// VIEW DID LOAD
override func viewDidLoad() {
super.viewDidLoad()
profileTableView.showsVerticalScrollIndicator = false
loadProfileControllerData()
profileTableSetUp()
// Do any additional setup after loading the view.
}
func loadProfileControllerData(){
if let path = Bundle.main.path(forResource: "CellDescriptor", ofType: "plist") {
cellDescriptors = NSMutableArray(contentsOfFile: path)
}
getIndicesOfVisibleRows()
profileTableView.reloadData()
}
// SHOW PARENT VISIBLE ROWS AND SAVE THERE ROW INDEX IN ARRAY
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors.objectEnumerator().allObjects as! [[[String:Any]]]{
var visibleRows = [Int]()
for row in 0..<currentSectionCells.count {
if currentSectionCells[row]["isVisible"] as! Bool == true {
visibleRows.append(row)
}
}
visibleRowsPerSection.append(visibleRows)
print(visibleRowsPerSection)
}
}
// GET REQUIRED OBJECT OF TYPE [String: Any]
func getCellDescriptorForIndexPath(indexPath: NSIndexPath) -> [String: Any] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptorss = cellDescriptors[indexPath.section] as! NSArray
let data = cellDescriptorss.object(at: indexOfVisibleRow) as! [String:Any]
return data
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//----------------------
// EXTENSION TO OUR PROFILE CLASS THAT DETERMINE OUR CLASS CONFIRM 2 IMPORTANT DELEGATES
extension profileViewController : UITableViewDelegate,UITableViewDataSource{
//MARK-: TABLE VIEW DELEGATE FUNCTIONS
// RETURN NUMBER OF SECTION IN TABLE VIEW
public func numberOfSections(in tableView: UITableView) -> Int{
if cellDescriptors.count != 0{
return cellDescriptors.count
}
else{
return 0
}
}
// RETURN NUMBER OF ROWS IN EACH SECTION OF TABLE VIEWS
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return visibleRowsPerSection[section].count
}
/* Return object of UITableViewCell that contains table SECTON data and USER profile data */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath: indexPath as NSIndexPath)
let menuCell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! yourCellClass
if currentCellDescriptor["cellIdentifier"] as! String == "parent"{
}
else if currentCellDescriptor["cellIdentifier"] as! String == "child"{
menuCell.backgroundColor = UIColor.clear
}
return menuCell
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let indexOfTappedRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptorss = cellDescriptors[indexPath.section] as! NSArray
var data = cellDescriptorss.object(at: indexOfTappedRow) as! [String:Any]
if data["isExpandable"] as! Bool == true{
var shouldExpandAndShowSubRows = false
if data["isExpanded"] as! Bool == true{
shouldExpandAndShowSubRows = false
(cellDescriptorss[indexOfTappedRow] as AnyObject).setValue(shouldExpandAndShowSubRows, forKey: "isExpanded")
}
for i in (indexOfTappedRow + 1)...(indexOfTappedRow + (data["additionalRows"] as! Int)) {
(cellDescriptorss[i] as AnyObject).setValue(shouldExpandAndShowSubRows, forKey: "isVisible")
}
}
getIndicesOfVisibleRows()
self.profileTableView.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: UITableViewRowAnimation.fade)
}

Thank You for helping me out, I was stuck at a point where the sections weren't expanding even after your help, so just made some changes in the syntax as Swift 3.0 is very specific about type casting hence the didSelectRowAt wasn't functioning properly. Here is the complete didSelectRowAt method. Happy coding.
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let indexOfTappedRow = visibleRowsPerSection[indexPath.section][indexPath.row]
if (cellDescriptors[indexPath.section] as! [[String: AnyObject]])[indexOfTappedRow] ["isExpandable"] as! Bool == true {
var shouldExpandAndShowSubRows = false
if (cellDescriptors[indexPath.section] as! [[String: AnyObject]])[indexOfTappedRow]["isExpanded"] as! Bool == false {
// In this case the cell should expand.
shouldExpandAndShowSubRows = true
}
((cellDescriptors[indexPath.section] as! NSMutableArray)[indexOfTappedRow] as AnyObject).setValue(shouldExpandAndShowSubRows, forKey: "isExpanded")
for i in (indexOfTappedRow + 1)...(indexOfTappedRow + ((cellDescriptors[indexPath.section] as! [[String: AnyObject]])[indexOfTappedRow]["additionalRows"] as! Int)) {
((cellDescriptors[indexPath.section] as! NSMutableArray)[i] as AnyObject).setValue(shouldExpandAndShowSubRows, forKey: "isVisible")
}
}

Swift 3/4 without use of NSMutable arrays based on the tutorial and all the code wrapped in a model.
class CellsDescriptorModel {
private var cellDescriptors: [[[String:Any]]]!
private var visibleRowsPerSection : [[Int]]
var CellDescriptors : [[[String:Any]]] { get { return cellDescriptors }}
var VisibleRowsPerSection : [[Int]] { get { return visibleRowsPerSection }}
init(plist:String) {
visibleRowsPerSection = [[Int]]()
if let url = Bundle.main.url(forResource:plist, withExtension: "plist") {
do {
let data = try Data(contentsOf:url)
cellDescriptors = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as! [[[String:Any]]]
} catch {
print(error)
}
}
getIndicesOfVisibleRows()
}
func getCellDescriptorForIndexPath(indexPath: IndexPath) -> [String: Any] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
return cellDescriptors[indexPath.section][indexOfVisibleRow]
}
func expandCell(indexPath:IndexPath) {
let indexOfTappedRow = visibleRowsPerSection[indexPath.section][indexPath.row]
if cellDescriptors[indexPath.section][indexOfTappedRow] ["isExpandable"] as! Bool == true {
var shouldExpandAndShowSubRows = false
if cellDescriptors[indexPath.section][indexOfTappedRow]["isExpanded"] as! Bool == false {
shouldExpandAndShowSubRows = true
}
cellDescriptors[indexPath.section][indexOfTappedRow]["isExpanded"] = shouldExpandAndShowSubRows
for i in (indexOfTappedRow + 1)...(indexOfTappedRow + (cellDescriptors[indexPath.section][indexOfTappedRow]["additionalRows"] as! Int)) {
cellDescriptors[indexPath.section][i]["isVisible"] = shouldExpandAndShowSubRows
}
}
else {
if cellDescriptors[indexPath.section][indexOfTappedRow]["cellIdentifier"] as! String == "DataPickerTableViewCell" {
var indexOfParentCell: Int!
for index in (0..<indexOfTappedRow).reversed() {
if cellDescriptors[indexPath.section][index]["isExpandable"] as! Bool == true {
indexOfParentCell = index
break
}
}
cellDescriptors[indexPath.section][indexOfParentCell]["secondaryTitle"] = ""
cellDescriptors[indexPath.section][indexOfParentCell]["isExpanded"] = false
for i in (indexOfParentCell + 1)...(indexOfParentCell + (cellDescriptors[indexPath.section][indexOfParentCell]["additionalRows"] as! Int)) {
cellDescriptors[indexPath.section][i]["isVisible"] = false
}
}
}
getIndicesOfVisibleRows()
}
private func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors {
var visibleRows = [Int]()
for row in 0..<currentSectionCells.count {
if currentSectionCells[row]["isVisible"] as! Bool == true {
visibleRows.append(row)
}
}
visibleRowsPerSection.append(visibleRows)
}
}
}

Related

Index out of range when i use to celll( Swift)

people, I have this issue when I try back image from different cell
(Thread 1: Fatal error: Index out of range)
what I'm doing here ?
I'm trying to build an Instagram clone and in my home view controller that what should posts show up. I make navigation with a table view and that table view has 2 cell with the different identifier. cell number 1 it's a header that brings data from users table to my username label and profile image. and cell number 2 its for posts its should bring post data like image and caption. I use firebase database.
my code :
import UIKit
import FirebaseAuth
import FirebaseDatabase
class HomeViewController: UIViewController ,UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
var posts = [Post]()
var users = [UserD]()
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
loadposts()
userDetal()
// var post = Post(captiontxt: "test", photoUrlString: "urll")
// print(post.caption)
// print(post.photoUrl)
}
func loadposts() {
Database.database().reference().child("posts").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let captiontxt = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let post = Post(captiontxt: captiontxt, photoUrlString: photoUrlString)
self.posts.append(post)
print(self.posts)
self.tableview.reloadData()
}
}
}
func userDetal() {
Database.database().reference().child("users").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let usernametxt = dict["username"] as! String
let profileImageUrlString = dict["profileImageUrl"] as! String
let user = UserD(usernametxt: usernametxt, profileImageUrlString: profileImageUrlString)
self.users.append(user)
print(self.users)
self.tableview.reloadData()
}
}
}
#IBAction func logout(_ sender: Any) {
do {
try Auth.auth().signOut()
}catch let logoutErrorr{
print(logoutErrorr)
}
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let signinVC = storyboard.instantiateViewController(withIdentifier: "SigninViewController")
self.present(signinVC, animated: true, completion: nil)
}
}
extension HomeViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableview.dequeueReusableCell(withIdentifier: "imagecell", for: indexPath) as! PostCellTableViewCell
cell.postimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.captionLabel.text = posts[indexPath.row].caption
let photoUrl = posts[indexPath.row].photoUrl
getImage(url: photoUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.postimage.image = photo
}
}
}
}
return cell
} else if indexPath.row == 1 {
let cell = tableview.dequeueReusableCell(withIdentifier: "postcell", for: indexPath) as! HeaderTableViewCell
cell.userimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.usernamelabel.text = users[indexPath.row].username
//Error showing here????????????????????????????????????
let profileImageUrl = users[indexPath.row].profileImageUrl
getImage(url: profileImageUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.userimage.image = photo
}
}
}
}
return cell
}
return UITableViewCell()
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
}
try this one.
cell.tag = indexpath.row
What is the content of users array ?
Are you sure you want to define as many sections as users or as many rows ?
In this case use
func numberOfRows(in tableView: NSTableView) -> Int {
return users.count
}
As explained, you need to rewrite completely cellForRowAt
It should look like this :
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if row < users.count {
let user = users[row]
if let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellID"), owner: self) {
(cellView as! NSTableCellView).textField?.stringValue = user.name
// do the same for all the fields you need to set
return cellView
} else {
return nil
}
}
return nil
}
thanx, my friend, I found a good way to contain my cell. for post cell, i just use cellForRowAt and but the post data. for header cell i use viewForHeaderInSection
and but my user data with heightForHeaderInSection. to make the high for a view

how to make checkmark to be selected depending on the array in swift 3?

I am having array in which selected name will be stored and passed to before view controller and when ever i need to go previous view controller then the previously selected check mark needs to be selected but here it is enabling the last selected element only the problem is if i select three then it is not selecting three it is check marking only the last element but i need the three selected can anyone help me how to make the check mark to be selected for three elements ?
protocol ArrayToPass: class {
func selectedArrayToPass(selectedStrings: [String])
}
class FilterSelectionViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
var productName = [String]()
var productprice = [String]()
var imageArray = [String]()
var idArray = [Int]()
let urlString = "http://www.json-generator.com/api/json/get/bOYOrkIOSq?indent=2"
var values = [String]()
var selected: Bool?
var delegate: ArrayToPass?
var nameSelection: Bool?
var namesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
tableDetails.separatorInset = UIEdgeInsets.zero
activityIndicator.startAnimating()
tableDetails.isHidden = true
tableDetails.dataSource = self
tableDetails.delegate = self
let rightBarButton = UIBarButtonItem(title: "Apply", style: UIBarButtonItemStyle.plain, target: self, action: #selector(applyBarButtonActionTapped(_:)))
self.navigationItem.rightBarButtonItem = rightBarButton
tableDetails.estimatedRowHeight = UITableViewAutomaticDimension
tableDetails.rowHeight = 60
// Do any additional setup after loading the view.
}
func applyBarButtonActionTapped(_ sender:UIBarButtonItem!){
self.delegate?.selectedArrayToPass(selectedStrings: values)
navigationController?.popViewController(animated: true)
}
func downloadJsonWithURL() {
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSArray {
for item in jsonObj! {
if let itemDict = item as? NSDictionary{
if let name = itemDict.value(forKey: "name") {
self.productName.append(name as! String)
}
if let price = itemDict.value(forKey: "value") {
self.productprice.append(price as! String)
}
if let image = itemDict.value(forKey: "img") {
self.imageArray.append(image as! String)
}
if let id = itemDict.value(forKey: "id") {
self.idArray.append(id as! Int)
}
}
}
OperationQueue.main.addOperation({
self.tableDetails.reloadData()
})
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filterSelectionCell", for: indexPath) as! FilterSelectionCell
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
tableDetails.isHidden = false
cell.brandProductName.text = productName[indexPath.row]
if nameSelection == true{
if namesArray.count != 0 {
print(namesArray)
for name in namesArray{
if productName[indexPath.row].contains(name){
print(productName[indexPath.row])
cell.accessoryType = .checkmark
}
else {
cell.accessoryType = .none
}
}
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
selected = false
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark{
cell.accessoryType = .none
print("\(productName[indexPath.row])")
values = values.filter{$0 != "\(productName[indexPath.row])"}
selected = true
}
else{
cell.accessoryType = .checkmark
}
}
if selected == true{
print(values)
}
else{
getAllTextFromTableView()
}
print(values)
}
func getAllTextFromTableView() {
guard let indexPaths = self.tableDetails.indexPathsForSelectedRows else { // if no selected cells just return
return
}
for indexPath in indexPaths {
values.append(productName[indexPath.row])
}
}
here is the image for this
Basically do not manipulate the view (the cell). Use a data model.
struct Product {
let name : String
let value : String
let img : String
let id : Int
var selected = false
init(dict : [String:Any]) {
self.name = dict["name"] as? String ?? ""
self.value = dict["value"] as? String ?? ""
self.img = dict["img"] as? String ?? ""
self.id = dict["id"] as? Int ?? 0
}
}
And never use multiple arrays as data source . That's a very bad habit.
Declare the data source array as
var products = [Product]()
Parse the JSON data and do a (better) error handling
func downloadJsonWithURL() {
let url = URL(string: urlString)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil { print(error!); return }
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
self.products = jsonObj.map{ Product(dict: $0) }
DispatchQueue.main.async {
self.tableDetails.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
in cellForRow... assign the name to the label and set the checkmark depending on selected
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filterSelectionCell", for: indexPath)
let product = products[indexPath.row]
cell.textLabel!.text = product.name
cell.accessoryType = product.selected ? .checkmark : .none
return cell
}
In didSelect... toggle selected and reload the row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selected = products[indexPath.row].selected
products[indexPath.row].selected = !selected
tableView.reloadRows(at: [indexPath], with: .none)
}
To get all selected items is very easy, too.
let selectedItems = products.filter{ $0.selected }
or get only the names
let selectedNames = products.filter{ $0.selected }.map{ $0.name }
There is no need at all to get any information from the view. The controller gets the information always from the model and uses tableview data source and delegate to update the view.
If you want to pass data to another view controller pass Product instances. They contain all relevant information.

In table view data was not loading and after json parsing the app was craashing

when i keep break points at the point mentioned in image then the data was loading without crashing the application and when i didn't kept the break points and make the application to run then it was crashing can any one help me how to clear the error
my code is shown below
let url = "http://www.json-generator.com/api/json/get/bUKEESvnvS?indent=2"
var orderdetailsArray : [[String:AnyObject]] = []
var itemsArray : [[String:AnyObject]] = []
var orderid = [Any]()
var orderdate = [Any]()
var subTotal = [Int]()
var shippingPrice = [Int]()
var tax = [Int]()
var grandTotal = [Int]()
var shippingAddress = [AnyObject]()
var shippingMethod = [AnyObject]()
var billingAddress = [AnyObject]()
var paymentMethod = [AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
tableDetails.delegate = self
tableDetails.dataSource = self
tableDetails.estimatedRowHeight = 600
// Do any additional setup after loading the view.
}
func downloadJsonWithURL() {
let url = NSURL(string: self.url)
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
self.orderdetailsArray = (jsonObj!.value(forKey: "Orders detail") as? [[String: AnyObject]])!
if let firstDictInfo = self.orderdetailsArray.first as? [String:Any] {
self.itemsArray = firstDictInfo["Items detail"] as! [[String : AnyObject]]
}
OperationQueue.main.addOperation({
self.tableDetails.reloadData()
})
}
}).resume()
}
func numberOfSections(in tableView: UITableView) -> Int{
return 3
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if (section == 0){
return ""
}
else if (section == 1){
return ""
}
else{
return "Ordered Items"
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int){
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = UIColor.darkGray
header.textLabel?.textAlignment = NSTextAlignment.center
header.textLabel?.font = UIFont(name: "Futura", size: 17)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if (section == 0){
return 1
}else if (section == 1){
return 1
}
else{
return itemsArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section == 0)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "ordercell", for: indexPath) as! OrdersTableViewCell
cell.orderDateLabel.text = orderdetailsArray[0]["OrderDate"] as? String
cell.orderIdLabel.text = orderdetailsArray[0]["OrderId"]! as? String
let totalPrice : Int = orderdetailsArray[0]["Shipping"]! as! Int
let price = Float(totalPrice)
cell.shippingLabel.text = "$" + "\(price)"
let subTotalPrice : Int = orderdetailsArray[0]["SubTotal"]! as! Int
let subtotalprice = Float(subTotalPrice)
cell.subTotalLabel.text = "$" + "\(subtotalprice)"
let taxPrice : Int = orderdetailsArray[0]["Tax"]! as! Int
let taxPriceFloat = Float(taxPrice)
cell.taxLabel.text = "$" + "\(taxPriceFloat)"
let grandTotal : Int = self.orderdetailsArray[0]["GrandTotal"]! as! Int
let grandPriceFloat = Float(grandTotal)
cell.grandTotalLabel.text = "$" + "\(grandPriceFloat)"
return cell
}
else if (indexPath.section == 1){
let cell = tableView.dequeueReusableCell(withIdentifier: "shippingcell", for: indexPath) as! ShippingTableViewCell
cell.shippingMethodLabel.text = orderdetailsArray[0]["ShippingMethod"] as? String
cell.shippingAddressLabel.text = orderdetailsArray[0]["ShippingAddress"]! as? String
cell.billingAddressLabel.text = orderdetailsArray[0]["BillingAddress"]! as? String
cell.paymentMethodLabel.text = orderdetailsArray[0]["PayMentMethod"]! as? String
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "orderdetailscell", for: indexPath) as! OrderDetailsTableViewCell
let array = itemsArray[indexPath.row]
let price : Int = array["ItemPrice"] as! Int
let grandPriceFloat = Float(price)
cell.priceLabel.text = "$" + "\(grandPriceFloat)"
let quant : Int = array["ItemQty"] as! Int
cell.quantityLabel.text = "\(quant)"
cell.productNameLabel.text = array["ItemName"] as? String
let subTotal : Int = array["ItemSubTotal"] as! Int
let subPriceFloat = Float(subTotal)
cell.subTotalLabel.text = "$" + "\(subPriceFloat)"
let grandTotal : Int = array["ItemSku"] as! Int
cell.skuLabel.text = "\(grandTotal)"
return cell
}
}
You need to confirm if orderDetailsArray actually contains some value before you are accessing it. Also, you are just returning 1 for numberOfRowsInSection without even checking if orderDetailsArray has some content. This should possibly resolve your crash:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if (section == 0 || section == 1) {
if orderDetailsArray.count > 0 {
return 1
} else {
return 0
}
}
else {
return itemsArray.count
}
}
I am not sure if this is the kind of behavior you are expecting because it will result in section Headers with no rows but, you can modify that!
Need to register UINib in UITableView for use multiple custom cell in tableView.
How to register UINib in UITableView?
Just write below three line in ViewDidLoad()
tableView.register(UINib(nibName: "ShippingTableViewCell", bundle: nil), forCellReuseIdentifier: "shippingcell")
tableView.register(UINib(nibName: "OrderDetailsTableViewCell", bundle: nil), forCellReuseIdentifier: "orderdetailscell")
tableView.register(UINib(nibName: "OrdersTableViewCell", bundle: nil), forCellReuseIdentifier: "ordercell")

Tableview not reloading data

I am stuck on this minor issue, I have a tableviewcontroller which is also searchresultcontroller. I am getting correct data against each api call but tableview is not reloading. I have no clue why its not working. Any help or lead will be very much appreciated.
class MasterViewController: UITableViewController,UISearchResultsUpdating {
var request:DataRequest?
var peopleArr:[Peoples] = []
// MARK: - View Setup
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Search"
definesPresentationContext = true
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 1 {
// if searchController.searchBar.selectedScopeButtonIndex == 0 {
let profileVc = self.storyboard?.instantiateViewController(withIdentifier: "profileVc") as! ProfileController
profileVc.profileData = (peopleArr[indexPath.row].user_id, peopleArr[indexPath.row].user_id)
self.navigationController?.pushViewController(profileVc, animated: true)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peopleArr.count
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableView.dequeueReusableCell(withIdentifier: "headerPeopleSec")
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "People"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowingsCell", for: indexPath) as! FollowingsCell
cell.textLabel?.text = "\(indexPath.row)"
let people: Peoples
people = peopleArr[indexPath.row]
if people.following == "1" {
cell.followBtn.isHidden = true
}
else{
cell.followBtn.isHidden = false
}
cell.profile_thumb!.showImageWithURL(urlString: people.photo_url)
cell.addAction = { cell in
self.addFriendAction(indexPath: indexPath , user:people)
}
cell.profile_thumb.motionIdentifier = people.user_id
cell.username.text = people.user_name
return cell
}
func getPeopleList(searchString:String?) {
if let req = self.request {
req.cancel()
}
let peopleBag = [
"auth_token": (MemberProfile.loggedUser?._auth_token())!,
"per_page": 30,
"page": 1,
"search_key": searchString ?? ""
] as [String : Any]
NVActivityIndicatorPresenter.sharedInstance.startAnimating(activityData)
self.request = HelperClass().doGetRequestCustom(url: BASE_URL + SEARCH_PEOPLE, param:peopleBag, header: [:], completion: {(response,responseObject, error) in
if let resMsg = (responseObject?.message.resp_status) {
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
// if let hasNext = responseObject?.message.paging_data.next_page_exist as? Bool {
// self.hasNextPage = hasNext
// }
let dictionary:[String: AnyObject]? = responseObject?.message.data as? [String:AnyObject] //["member_followings"]
if let dict:Array = dictionary?["member_profiles"] as? Array<[String:AnyObject]>{
for dic in dict {
let friend = Peoples()
friend.photo_url = (dic["photo"] as? String) ?? ""
friend.user_name = ((dic["user"]?["username"])! as String)
friend.user_id = (dic["id"])! as! String
friend.following = (dic["is_following"])! as! String
self.peopleArr.append(friend)
}
self.tableView.reloadData()
}
else{
}
}
else{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
}
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
})
}
func addFriendAction(indexPath:IndexPath , user:Peoples) {
let followBag = [
"auth_token": (MemberProfile.loggedUser?.auth_token)!,
"following_profile_id": user.user_id
] as [String : Any]
NVActivityIndicatorPresenter.sharedInstance.startAnimating(activityData)
HelperClass().doPostRequest(url: BASE_URL+FOLLOW_MEMBER , param: followBag, completion: { (dataResponse,response,error) in
if (response != nil) && (response?.message.resp_status)!
{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
let cell = self.tableView.cellForRow(at: indexPath) as! FollowingsCell
cell.followBtn.isHidden = true
user.following = "1"
}
else
{
if (response != nil){
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
HelperClass.showAlertViewWithTitle(title: "Error", Text: (response?.message.message)!, controllerToShowOn: self)
}
else{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
HelperClass.showAlertViewWithTitle(title: "Error", Text: "Something went wrong. Please check your internet connection & try again later.", controllerToShowOn: self)
}
return
}
})
}
func updateSearchResults(for searchController: UISearchController) {
if !(searchController.searchBar.text! == "") {
self.peopleArr.removeAll()
self.tableView.reloadData()
let searchBar = searchController.searchBar
self.getPeopleList(searchString: searchBar.text!)
}
}
}
You need to make your reload call on the main thread:
...
for dic in dict {
let friend = Peoples()
friend.photo_url = (dic["photo"] as? String) ?? ""
friend.user_name = ((dic["user"]?["username"])! as String)
friend.user_id = (dic["id"])! as! String
friend.following = (dic["is_following"])! as! String
self.peopleArr.append(friend)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
...
All UI modification always has to take place on the main thread. Most of the time you're in a completion handler you'll have to dispatch to main to modify the UI.
Seems something wrong in func updateSearchResults(for searchController: UISearchController).
Can you try moving self.tableView.reloadData() at the end of this function ?
It seems when reloadData is called, the array as cleared, and not yet populated with new values.

Swift: Multiple Custom TableViewCells from XIB files

I am following this tutorial from Jared Davidson to implement multiple CustomTableViewCells with XIB files in my app. I have these files in my Xcode project:.
I have a TextElement: and
I have an ImageElement:
I want to test this with offline data to implement Firebase after this is working. This is my Home.swift data struct:
import Foundation
import FirebaseDatabase
struct Home {
var key:String!
let itemRef:FIRDatabaseReference?
var userUID:String!
var user:String!
// Home Element Cell Content
var elementSortNumber:Int!
var elementCellType:String!
var referenceElementID:String!
var databaseVersion:String!
init (key:String = "",
uid:String,
user:String,
elementSortNumber:Int,
elementCellType:String,
referenceElementID:String,
databaseVersion:String) {
// General (Security tracking)
self.key = key
self.itemRef = nil
self.userUID = uid
self.user = user
// Home Element Cell Content
self.elementSortNumber = elementSortNumber
self.elementCellType = elementCellType
self.referenceElementID = referenceElementID
}
init (snapshot:FIRDataSnapshot) {
// General (Security tracking)
key = snapshot.key
itemRef = snapshot.ref
if let addedByUser = snapshot.value as? NSDictionary, let _temp = addedByUser["User"] as? String {
user = _temp
} else {
user = ""
}
// Home Element Cell Content
if let homeElementSortNumber = snapshot.value as? NSDictionary, let _temp = homeElementSortNumber["Title"] as? Int {
elementSortNumber = _temp
} else {
elementSortNumber = 50
}
if let homeElementCellType = snapshot.value as? NSDictionary, let _temp = homeElementCellType["Content"] as? String {
elementCellType = _temp
} else {
elementCellType = ""
}
if let homeElementID = snapshot.value as? NSDictionary, let _temp = homeElementID["Ref Element ID"] as? String {
referenceElementID = _temp
} else {
referenceElementID = ""
}
if let textDatabaseVersion = snapshot.value as? NSDictionary, let _temp = textDatabaseVersion["DB Version"] as? String {
databaseVersion = _temp
} else {
databaseVersion = ""
}
}
}
This is the code of my TableViewController:
import UIKit
class HomeTableViewController: UITableViewController {
var arrayOfCellData = [Home]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
arrayOfCellData =
[Home(key: "",
uid:"",
user:"",
elementSortNumber:1,
elementCellType:"TextElement",
referenceElementID:"123ABC",
databaseVersion:"1"),
Home(key: "",
uid:"",
user:"",
elementSortNumber:1,
elementCellType:"ImageElement",
referenceElementID:"QWERTZ",
databaseVersion:"1"),
Home(key: "",
uid:"",
user:"",
elementSortNumber:1,
elementCellType:"TextElement",
referenceElementID:"XYZ789",
databaseVersion:"1")]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// If I return 1 the app crashes and if I comment this function it also crashes.
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfCellData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if arrayOfCellData[indexPath.row].elementCellType == "TextElement" {
let textElementCell = Bundle.main.loadNibNamed("TextElementTableViewCell", owner: self, options: nil) as! TextElementTableViewCell
textElementCell.textElementTitleLabel.text = arrayOfCellData[indexPath.row].referenceElementID
return textElementCell
}
else if arrayOfCellData[indexPath.row].elementCellType == "ImageElement" {
let imageElementCell = Bundle.main.loadNibNamed("ImageElementTableViewCell", owner: self, options: nil) as! ImageElementTableViewCell
imageElementCell.imageElementImageView.image = UIImage(named: "placeholder")
return imageElementCell
}
else {
let textElementDefaultCell = Bundle.main.loadNibNamed("TextElementTableViewCell", owner: self, options: nil) as! TextElementTableViewCell
textElementDefaultCell.textElementTitleLabel.text = arrayOfCellData[indexPath.row].referenceElementID
return textElementDefaultCell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if arrayOfCellData[indexPath.row].elementCellType == "TextElement" {
return 116
}
else if arrayOfCellData[indexPath.row].elementCellType == "ImageElement" {
return 275
}
else {
return 116
}
}
}
This is the problem: The simulator is empty as you can see in this image Why? How can I fix that?
I would really appreciate some help. Thank you.
Register the xib files as below in viewdidload:
tableView.register(UINib(nibName: "TextElementTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "TextElementTableViewCellIdentifier")
Then in cellForRowIndex path:Access cell using their identifier
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "TextElementTableViewCellIdentifier", for: indexPath) as! TextElementTableViewCell

Resources