I'm working on displaying data from a plist in multiple "drill down" tableViews. The displayed data is only going to be read-only and I don't want it to necessarily be based on web data, and there will only be a maximum of about 100 data points so I'm fairly happy with plist instead of JSON.
Anyway, the question... I've got an array of dictionary items that I'm managing to display fairly well. I started with the GitHub relating to the following question on stack overflow (I modified it a little bit but thats not important).
Load data from a plist to two TableViews
https://github.com/DonMag/SWPListData
What I need help with is displaying an array of items ("friends" in this example) under each person. Code below will hopefully explain.
I've created an empty array in the model
struct EmployeeDetails {
let functionary: String
let imageFace: String
let phone: String
//This is the new array I've added and am having trouble displaying
let friends: [String]
init(dictionary: [String: Any]) {
self.functionary = (dictionary["Functionary"] as? String) ?? ""
self.imageFace = (dictionary["ImageFace"] as? String) ?? ""
self.phone = (dictionary["Phone"] as? String) ?? ""
//I've initialised it here.
self.friends = (dictionary["Friends"] as? [String]) ?? [""]
Now in the viewController displaying the data. I don't have any problems at all displaying the correct data for the "functionary", "imageFace" and "phone" - but I just can't seem to display "friends" as an array in its own section. I'm pretty sure the main problem is in numberOfRows and cellForRow:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if let theEmployee = newPage {
return theEmployee.details.count
}
return 0
}
else if section == 1 {
return 1
}
else if section == 2 {
if let theEmployee = newPage {
return theEmployee.details.count
}
return 0
}
else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // as! TableViewCell2
if indexPath.section == 0 {
cell.textLabel?.text = "A"
if let theEmployee = newPage {
cell.textLabel?.text = theEmployee.details[indexPath.row].functionary
cell.detailTextLabel?.text = theEmployee.details[indexPath.row].phone + " (" + theEmployee.details[indexPath.row].imageFace + ")"
}
}
else if indexPath.section == 1 {
cell.textLabel?.text = "Test"
}
else if indexPath.section == 2 {
cell.textLabel?.text = ????
}
return cell
}
I thought it would work writing the following in numberOfRows:
else if section == 2 {
if let theEmployee = newPage {
return theEmployee.details.friends.count
}
return 0
}
But I get the error:
value of type '[EmployeeDetails]' has no member 'friends'.
What do I need to do to get that array?
Note: The array is not empty.
Any suggestions/help would be much appreciated!
Problem is that you are accessing the friends property on details whereas it is a property on the structs stored inside details so you would have to access it through indexing something like this
theEmployee.details[yourIndex].friends.count
Update:
//First you need to make changes inside the .plist file, please go there and add Friends Array inside just like Details
Now this will require you to change your Employee struct code
struct Employee {
let position: String
let name: String
let details: [EmployeeDetails]
let friends: [String]
init(dictionary: [String: Any]) {
self.position = (dictionary["Position"] as? String) ?? ""
self.name = (dictionary["Name"] as? String) ?? ""
let t = (dictionary["Details"] as? [Any]) ?? []
self.details = t.map({EmployeeDetails(dictionary: $0 as! [String : Any])})
self.friends = (dictionary["Friends"] as? [String]) ?? []
}
}
Next step would be to add this in the table view as follows
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let theEmployee = newPage {
if section == 0 {
return theEmployee.details.count
}else if section == 1 {
return theEmployee.friends.count
}
}
return 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Subordinates"
}else if section == 1 {
return "Friends"
}
return ""
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // as! TableViewCell2
cell.textLabel?.text = "A"
if let theEmployee = newPage {
if indexPath.section == 0 {
cell.textLabel?.text = theEmployee.details[indexPath.row].functionary
cell.detailTextLabel?.text = theEmployee.details[indexPath.row].phone + " (" + theEmployee.details[indexPath.row].imageFace + ")"
}else if indexPath.section == 1 {
cell.textLabel?.text = theEmployee.friends[indexPath.row]
}
}
return cell
}
}
Related
I start learning swift with Paul Hudson's "100 Days of Swift" and I need your advices.
I'm trying making app with country's info (capital, language, currencies etc.) and stuck with trying represent result of my JSON parsing in tableView.
This is my struct for parsing country's info from https://restcountries.com/v3.1/all
struct Country: Codable {
let name: Name
let cca2: String
let capital: [String]?
let population: Int
let currencies: [String: Currency]?
}
struct Name: Codable {
let common: String
let official: String
}
struct Currency: Codable {
let name: String?
let symbol: String?
}
I have problems with currencies. I don't understand how represent them properly in tableView. This is code of my ViewController:
import UIKit
class ViewController: UITableViewController {
var countries = [Country] ()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://restcountries.com/v3.1/all"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
return
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
countries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Country", for: indexPath)
let country = countries[indexPath.row]
cell.textLabel?.text = country.name.common
cell.imageView?.image = UIImage(named: country.cca2.lowercased())
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.country = countries[indexPath.row]
present(vc, animated: true)
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
do {
let jsonCountries = try decoder.decode([Country].self, from: json)
countries = jsonCountries
}
catch let error {
print(error)
}
}
}
And this is code of my DetailViewController:
import UIKit
class DetailViewController: UITableViewController {
var country: Country!
let flag = "Flag"
let general = "General"
let currency = "Currency"
var currencyName = ""
var currencySymbol = ""
lazy var sectionTitles = [flag, general, currency]
lazy var currencies = country.currencies?.values
override func viewDidLoad() {
super.viewDidLoad()
title = country.name.common
getCurrencyName()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sectionTitles[section] {
case flag:
return 1
case general:
return 4
case currency:
// How make to return proper number's of rows??
return 2
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sectionTitles[indexPath.section] {
case flag:
let cell = tableView.dequeueReusableCell(withIdentifier: "Flag", for: indexPath)
if let cell = cell as? FlagCell {
cell.flagImageView.image = UIImage(named: country.cca2.lowercased())
}
return cell
case general:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
cell.textLabel?.text = "Common country name: \(country.name.common)"
case 1:
cell.textLabel?.text = "Official country name: \(country.name.official)"
case 2:
cell.textLabel?.text = "Capital: \(country.capital?[0] ?? "Unknown")"
case 3:
cell.textLabel?.text = "Population: \(country.population) people"
default:
return cell
}
return cell
case currency:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
// How to represent each currency of country?
cell.textLabel?.text = "Currency name: \(currencyName)"
case 1:
cell.textLabel?.text = "Currency symbol: \(currencySymbol)"
default:
return cell
}
return cell
default:
break
}
return UITableViewCell ()
}
func getCurrencyName () {
for currency in currencies! {
currencyName = currency.name ?? ""
currencySymbol = currency.symbol ?? ""
}
}
}
For now I understand how to represent one currency of each country, but how I can represent all currencies of each country in different rows?
Sorry for my English it's not my native language :)
I would suggest getting a sorted list of the currencies. E.g., for a given Country:
let currencies = country.currencies?.sorted { $0.0 < $1.0 }
To get the count:
let count = currencies?.count ?? 0
To get the details for a particular row, it would be:
if let (code, currency) = currencies?[indexPath.row] {
let currencyCode = code
let currencyName = currency.name
let currencySymbol = currency.symbol
}
You can access the number of currencies for each country with this, and use it in the numberOfRowsInSection method to return enough number of rows for currencies:
country.currencies.count
The rest is filling the cells in the cellForRowAt method by using the indexPath's section and row values. You should iterate over the currencies dictionary of the selected country, and display each key and value pair in the dictionary in a row.
You can do the iteration like so:
for (key, value) in dict {
// Use key and value here
}
I wanted to know how to load selected data from my table view, for example when selecting segment index is equal 1, the table view will reload and will only show data which status is equal to approved. Cause as you have seen from my below code, I have loaded all the data with all the statuses: . for example if segmentView.selectedSegmentIndex == 1 table will reload with the data which status is equal to approved. I could already determine the selected index. what i want is how to access those data from the table view that i could load selected data depending to status
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "ToDoListTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ToDoListTableViewCell
cell.delegate = self
// let toDoActionItem = fetchedResultsController.object(at: indexPath)
if let getTempDetails: [String : Any] = getAllDetail[indexPath.row] {
print("ang tanan data:" , getTempDetails)
if let str = getTempDetails["status"] as? [String: String] {
if let name = str["name"] {
if name == "ongoing" {
cell.toDoItemLabel.text = getTempDetails["name"] as? String
cell.statuslabel.backgroundColor = created
// cell.label.textColor = UIColor(red: 0.9294, green: 0.3333, blue: 0.1804, alpha: 1.0)
// cell.backgroundColor = created
}
else if name == "approved" {
cell.toDoItemLabel.text = getTempDetails["name"] as? String
cell.statuslabel.backgroundColor = done
cell.checkBoxButton.isSelected = true
}
else if name == "for approval" {
cell.toDoItemLabel.text = getTempDetails["name"] as? String
cell.statuslabel.backgroundColor = pending
}else if name == "near expiry" {
cell.toDoItemLabel.text = getTempDetails["name"] as? String
cell.statuslabel.backgroundColor = neardue
} else if name == "expired" {
cell.toDoItemLabel.text = getTempDetails["name"] as? String
cell.statuslabel.backgroundColor = expired
} else {
print("false")
cell.toDoItemLabel.text = "LOLS"
}
}
}
}
code for segment (in selecting segment)
func selectSegmentInSegmentView(segmentView: SMSegmentView) {
if segmentView.selectedSegmentIndex == 1 {
print("ang index nga emo ge click is one")
// let selectedSegment : SMSegment = segmentView.selectedSegment!
// self.userName = selectedSegment.label.text!
} else {
logic here
}
self.setUpTableView()
or could be self.tableView.reloadData()
}
The best option Would be like:
make a common array for Display, that will be used to display the data in tableView:
and the Use it like this for all the conditions like:
make a Common Function like this:
func filterDataStatusWise(strStatus:String){
for dict in arrMainResponse{
let strStatus = arrMainResponse["status"]
if strStatus["name"] = strStatus{
//Whatever data add in arrForDisplay here
}
tableView.reloadData()
}
}
And then Use it like this:
var arrForDisplay = [String:Any]()
if condition1{
filterDataStatusWise(strStatus: "ongoing")
}else if condition2{
filterDataStatusWise(strStatus: "approved")
}else if condition3{
filterDataStatusWise(strStatus: "for approval")
}else if condition4{
filterDataStatusWise(strStatus: "near expiry")
}else{
filterDataStatusWise(strStatus: "expired")
}
Hope it helps!
You just need to create 5 separate arrays to load while selection of different segment of UISegmentControl.
var ongoingArr = [[String: Any]]() // For segment index 0
var approvedArr = [[String: Any]]() // For segment index 1
var forApprovalArr = [[String: Any]]() // For segment index 2
var nearExpiryArr = [[String: Any]]() // For segment index 3
var expiredArr = [[String: Any]]() // For segment index 4
You have the whole data getAllDetail, and you are getting the data in this array by API or previous screen:
var getAllDetail = [[String: Any]]()
If you are getting the data from API, then load above 5 arrays after loading the data in getAllDetail. Fo that just create an extension of array as:
extension Array where Element == [String: Any] {
func filterArray(_ statusName: String) -> [Element] {
return self.filter { infoDict -> Bool in
if let statusDict = infoDict["status"] as? [String: String], let name = statusDict["name"] {
return name == statusName
}
return false
}
}
}
and, load above 5 arrays:
func loadSegmentArray() {
ongoingArr = getAllDetail.filterArray("ongoing")
approvedArr = getAllDetail.filterArray("approved")
forApprovalArr = getAllDetail.filterArray("for approval")
nearExpiryArr = getAllDetail.filterArray("near expiry")
expiredArr = getAllDetail.filterArray("expired")
// Select 0th index of segment and reload table
segmentView.selectedSegmentIndex = 0
self.setUpTableView() // Reload Table view
}
In your func selectSegmentInSegmentView, just reload table view:
func selectSegmentInSegmentView(segmentView: SMSegmentView) {
self.setUpTableView()
}
And update your UITabelView delegate and datasource methods, according to these 5 arrays.
For Sample, I am writing numberOfRowsInSection and cellForRowAt indexPath:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch segmentView.selectedSegmentIndex {
case 0:
return ongoingArr.count
case 1:
return approvedArr.count
case 2:
return forApprovalArr.count
case 3:
return nearExpiryArr.count
case 4:
return expiredArr.count
default:
return 0
}
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ToDoListTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ToDoListTableViewCell
cell.delegate = self
var infoDict = [String: Any]()
switch segmentView.selectedSegmentIndex {
case 0:
infoDict = ongoingArr[indexPath.row]
cell.statuslabel.backgroundColor = // Color of onging
case 1:
infoDict = approvedArr[indexPath.row]
cell.statuslabel.backgroundColor = // Color of approvedArr
case 2:
infoDict = forApprovalArr[indexPath.row]
cell.statuslabel.backgroundColor = // Color of forApprovalArr
case 3:
infoDict = nearExpiryArr[indexPath.row]
cell.statuslabel.backgroundColor = // Color of nearExpiryArr
case 4:
infoDict = expiredArr[indexPath.row]
cell.statuslabel.backgroundColor = // Color of expiredArr
default:
cell.statuslabel.backgroundColor = .black
}
cell.toDoItemLabel.text = infoDict["name"] as? String
return cell
}
Filter array with respect to selected index(status):
func tableView(tableView: UITableView, numberOfRowsInSection section:
Int) -> Int {
switch segmentView.selectedSegmentIndex {
case 0:
if self.expiredArray == nil {
return 0
}
return (self.expiredArray?.count)!
case 1:
if self.approvedArray == nil {
return 0
}
return (self.complaintArray?.count)!
default:
break
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch segmentView.selectedSegmentIndex {
case 0:
// Load from expired array
case 1:
// Load from approved array
default:
break
}
return UITableViewCell()
}
func selectSegmentInSegmentView(segmentView: SMSegmentView) {
if segmentView.selectedSegmentIndex == 1 {
// Write your code to filter array here based on status
} else {
// Write your code to filter array here based on status
}
// reload tableview here.....
}
In the code below, I query for all new posts using the posted date and childAdded, but it seems that when there isn't a new item, the table view doesn't show any items but the firebase block still runs? Please help. I did add number of rows in section. I don't know why stack overflow won't show.
FIRDatabase.database().reference().child("posts").child(uid!).queryOrdered(byChild: "postedDate").observe(.childAdded, with: {(snapshot) in
if (snapshot.hasChildren()) {
print("Has children")
if let dictionary = snapshot.value as? [String: AnyObject] {
let imageCount = dictionary["Images"]?.count
// Post not saved
if (imageCount == nil) {
print("image count is nil")
}
// better catch
else if (imageCount! < 3) {
print("not all 3 were posted")
}
// Saved post
else {
// Adding to details array
self.keyArray.append(snapshot.key)
// Inside a for loop to get every Image
for index in 1 ..< dictionary["Images"]!.count + 1 {
// Check = image1, image2 ...
let check = "image" + String(index)
// Put into the imageArray dictionary
self.imageArray[check] = dictionary["Images"]?[check] as! String?
}
// Handles adding the key and appending the image
self.imageArray2[snapshot.key] = self.imageArray
self.imageArray3.append(self.imageArray2)
// No images but actual values
// Creating makeEasy variables
var valuesArray = [String: String]()
// Adding values to specific array
let name = dictionary["Itemname"] as? String?
// I have all items
if (name != nil) {
// Add values to valueArray
valuesArray["name"] = name!
// Add the Key: UUID
self.details[snapshot.key] = valuesArray
// Then put in the actual array
self.detailsArr.append(self.details)
}
// I DONT have all items
else {
print("Not present!!")
}
// Removing
self.imageArray.removeAll()
self.imageArray2.removeAll()
valuesArray.removeAll()
self.details.removeAll()
}
}
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
else {
print("Has NO children")
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.imageArray3.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "viewTableCell", for: indexPath) as! viewTableViewCell
// get the key first and CHECK IF IT EXISTS ******
let key = keyArray[indexPath.row]
cell.name.text = self.detailsArr[indexPath.row][key]!["name"]
// cache the pic
if let pic = self.imageArray3[indexPath.row][key]?["image1"] {
cell.itemImage.load(pic)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the key and the index
self.tappedIndex = indexPath.row
self.currentKey = keyArray[indexPath.row]
self.performSegue(withIdentifier: "mystify", sender: self)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 400
}
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)
}
}
}
I have a list of data coming in from json. With this list I would like to load it into a tableview but within two seperate sections called Featured and All. Trying to figure out how to get my "Featured" section not to load the same amount of rows as the "All" section. All section is good but Featured section shows Featured list plus 16 empty rows. Any ideas on how I get rid of these extra rows in the Featured section?
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(section == 0)
{
return "Featured"
}
return "All"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
var sections: Int = 2
return sections
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count: Int = tableData.count
println("tabledata \(count)")
return count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//let session = NSURLSession.sharedSession()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
let entry : NSMutableDictionary = self.tableData[indexPath.row] as! NSMutableDictionary
var featured = entry["Business_IsFeatured"] as? String
if ((featured == "1") && (indexPath.section == 0))
{
var busName = entry["Business_Name"] as? String
var points = entry["Member_Points"] as? String
var imageName = entry["Business_Image"] as? String
var imgURL: NSURL = NSURL(string: "http://www.example.com/images/\(imageName!)")!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(
request, queue: NSOperationQueue.mainQueue(),
completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
cell.textLabel!.text = busName
cell.textLabel!.numberOfLines = 0
cell.detailTextLabel!.text = "Points: \(points!)"
cell.imageView!.image = UIImage(data: data)
}
})
}
else
{
if((featured == "0") && (indexPath.section == 1))
{
var busName = entry["Business_Name"] as? String
var points = entry["Member_Points"] as? String
var imageName = entry["Business_Image"] as? String
var imgURL = NSURL(string: "http://www.example.com/images/\(imageName!)")
cell.textLabel!.text = busName
cell.textLabel!.numberOfLines = 0
cell.detailTextLabel!.text = "Points: \(points!)"
cell.imageView!.hnk_setImageFromURL(imgURL!, format: Format<UIImage>(name: "original"))
}
}
return cell
}
Your implementation of UITableViewDataSource's
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
method needs to supply the number of rows you intend for each section to contain. Your code returns the same number of rows regardless of the section, which sounds wrong based on how you describe what you want to do. You need to calculate and return the number of featured items for your Featured section, rather than the size of your entire dataset.
The "tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int" function is here for this. Depending of the section, you return the numbers of rows into this section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(section==0){
var count: Int = tableData.count
println("tabledata \(count)")
return count
}else{
return theOtherData.count
}
}
#tomsoft and #ScorpioCurse I was able to get the featured section to load up the correct data by making two seperate arrays for the data. Before I was trying to return count which swift wasnt liking. After changing it to return a separate array tableFeatData.count else return tableData.count I got it working. I also had to add a second dictionary. Code below. I may still need to clean up some of this code.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//let session = NSURLSession.sharedSession()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
let entry : NSMutableDictionary = self.tableData[indexPath.row] as! NSMutableDictionary
var featured = entry["Business_IsFeatured"] as? String
if ((featured != "1") && (indexPath.section == 0))
{
let featentry: NSMutableDictionary = self.tableFeatData[indexPath.row] as! NSMutableDictionary
var busName = featentry["Business_Name"] as? String
var points = featentry["Member_Points"] as? String
var imageName = featentry["Business_Image"] as? String
var imgURL: NSURL = NSURL(string: "http://www.example.com/images/\(imageName!)")!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(
request, queue: NSOperationQueue.mainQueue(),
completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
cell.textLabel!.text = busName
cell.textLabel!.numberOfLines = 0
cell.detailTextLabel!.text = "Points: \(points!)"
cell.imageView!.image = UIImage(data: data)
}
})
}
else
{
var busName = entry["Business_Name"] as? String
var points = entry["Member_Points"] as? String
var imageName = entry["Business_Image"] as? String
var imgURL = NSURL(string: "http://www.example.com/images/\(imageName!)")
cell.textLabel!.text = busName
cell.textLabel!.numberOfLines = 0
cell.detailTextLabel!.text = "Points: \(points!)"
cell.imageView!.hnk_setImageFromURL(imgURL!, format: Format<UIImage>(name: "original"))
}
return cell
}