Unable to Expand and Collapse TableViewCell in Swift - json

I have taken two prototype cells called CategoryTableCell and SubCategoryTableCell in tableview
Initially, I need to show all CategoryTableCell data in TableView. Here if I click CategoryTableCell then I need to show that Category's subcategories in SubCategoryTableCell. How to do that?
I need to hide all subcategories. If I click any one category from CategoryTableCell then I need to its subcategories.
Code:
extension CategoryVC: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return self.activeCategories?.count ?? 0 : 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.activeCategories?[section].sub_categories?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubCategoryTableCell", for: indexPath) as! SubCategoryTableCell
cell.selectionStyle = .none
let activesubCat = self.activeCategories?[indexPath.section].sub_categories
let indexData = activesubCat?[indexPath.row]
cell.lblTitle?.text = langType == .en ? indexData?.details?.first?.title : indexData?.details?[1].title
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableCell(withIdentifier: "CategoryTableCell") as! CategoryTableCell
let indexData = self.activeCategories?[section]
view.btnDetails?.tag = section
view.lblTitle?.text = langType == .en ? indexData?.details?.first?.title : indexData?.details?[1].title
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return isFromFilter ? 40 : 0
}
With the code, I am getting all categories in red colour and subcategories in dark grey colour text. But, here cell is not expanding and collapse. I need to show all red colour text initially. Here if I tap on the cell then it has to expand and show all grey colour text. How to do that? Please do help with the code.
o/p: coming like this with above code
[![screenshot][1]][1]
EDIT
JSON Model:
public class Category_json {
public var id : Int?
public var status : String?
public var sub_categories : Array<Sub_categories>?
var isExpanded = true
required public init?(dictionary: NSDictionary) {
id = dictionary["id"] as? Int
status = dictionary["status"] as? String
if (dictionary["sub_categories"] != nil) { sub_categories = Sub_categories.modelsFromDictionaryArray(array: dictionary["sub_categories"] as? NSArray ?? NSArray()) }
if (dictionary["details"] != nil) { details = Details.modelsFromDictionaryArray(array: dictionary["details"] as? NSArray ?? NSArray()) }
}
public func dictionaryRepresentation() -> NSDictionary {
let dictionary = NSMutableDictionary()
dictionary.setValue(self.id, forKey: "id")
dictionary.setValue(self.status, forKey: "status")
return dictionary
}
}
and public var activeCategories : Array<Category_json>?
EDIT2:
#objc func expandMe() {
print("expand me")
}

Add a property to your activeCategories array 's model
var isExpanded = true
Then inside numberOfRowsInSection
guard let item = self.activeCategories?[section] else { return 0 }
return item.isExpanded ? (item.sub_categories?.count ?? 0) : 0
Then play with isExpanded and refresh the table
Edit
Inside viewForHeaderInSection
let header = tableView.dequeueReusableCell(withIdentifier: "CategoryTableCell") as! CategoryTableCell
button.tag = section
Then inside action
#objc headerClicked(_ sender:UIButton) {
let section = sender.tag
guard let item = self.activeCategories?[section] else { return 0 }
item.isExpanded.toggle()
tableView.reloadData()
}

Related

Issue adding sections to tableview from JSON data

I am trying to group my table view that is being populated from JSON data.
Here is an example of what it looks like:
[{"customer":"Customer1","serial":"34543453",
"rma":"P2384787","model":"M282","manufacturer":"Manufacturer1"},
{"customer":"Customer1","serial":"13213214",
"rma":"P2384787","model":"M384","manufacturer":" Manufacturer1"},
{"customer":"Customer2","serial":"1212121323",
"rma":"P3324787","model":"M384","manufacturer":" Manufacturer1"}]
I would like to group the table view based on the customer name.
So in my case, it should look like:
Customer1
34543453 - Manufacturer1 - M282
13213214 - Manufacturer1 - M384
Customer2
1212121323 - Manufacturer1 - M384
NOTE:
The reason there is a line separating the serial manufacturer and model is because of this separator in CustomerViewController.swift:
let titleStr = [item.serial, item.manufacturer, item.model].compactMap { $0 }.joined(separator: " - ")
PortfolioController.swift
import UIKit
class PortfolioController: UITableViewController {
var portfolios = [Portfolios]()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.title = "Customer"
fetchJSON()
}
func fetchJSON(){
let urlString = "https://www.example.com/example/example.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to fetch data from url", error)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.portfolios = try decoder.decode([Portfolios].self, from: data)
self.tableView.reloadData()
} catch let jsonError {
print("Failed to decode json", jsonError)
}
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return portfolios.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
let customer = portfolios[indexPath.row]
//cell.textLabel?.text = customer.serial
let titleStr = [customer.serial, customer.manufacturer, customer.model].compactMap { $0 }.joined(separator: " - ")
print(titleStr)
// Get references to labels of cell
cell.textLabel!.text = titleStr
return cell
}
}
Portfolios.swift
import UIKit
struct Portfolios: Codable {
let customer, serial, rma, model: String
let manufacturer: String
}
1- Create an instance var
var portfoliosDic = [String:[Portfolios]]()
2- Assign it here
let res = try JSONDecoder().decode([Portfolios].self, from: data)
self.portfoliosDic = Dictionary(grouping: res, by: { $0.customer})
DispatchQueue.main.async {
self.tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return portfoliosDic.keys.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let keys = Array(portfoliosDic.keys)
let item = portfoliosDic[keys[section]]!
return item.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
let keys = Array(portfoliosDic.keys)
let arr = portfoliosDic[keys[indexPath.section]]!
let customer = arr[indexPath.row]
//cell.textLabel?.text = customer.serial
let titleStr = [customer.serial, customer.manufacturer, customer.model].compactMap { $0 }.joined(separator: " - ")
print(titleStr)
// Get references to labels of cell
cell.textLabel!.text = titleStr
return cell
}

Trouble With SearchBar and Search Bar Controller Due to Depreciation of SearchDisplayController

I would like some help with the search bar functionality. I am stuck and not sure where to take it from here. I am trying to update the tableview when search text word is contained in a recipe title. I am not sure how to do this because of the depreciated searchDisplay controller. Help would be appreciated.
import UIKit
import SwiftyJSON
class Downloader {
class func downloadImageWithURL(_ url:String) -> UIImage! {
let data = try? Data(contentsOf: URL(string: url)!)
return UIImage(data: data!)
}
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate,UISearchDisplayDelegate{
#IBOutlet weak var recipeTable: UITableView!
// search functionality Need help with my search functionality
var filteredRecipes = [Recipe]()
func filterContentForSearch(searchText:String) {
// need help here
self.filteredRecipes = self.recipes.filter({(title:Recipe) -> Bool in
return (title.title!.lowercased().range(of: searchText.lowercased()) != nil)
})
}
private func searchDisplayController(controller: UISearchController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
self.filterContentForSearch(searchText: searchString)
return true
}
//end search parameters
// tableview functionionalitys
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == searchDisplayController!.searchResultsTableView {
return filteredRecipes.count
}else{
return recipes.count
}
// recipeTable.reloadData()
}
// tableview functionalities
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RecipeTableViewCell
if tableView == self.searchDisplayController!.searchResultsTableView{
//get images from download
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(self.filteredRecipes[indexPath.row].imageUrl)
}
cell.recipeLabel.text = self.filteredRecipes[indexPath.row].title
recipeTable.reloadData()
}else{
//get image from download
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(self.recipes[indexPath.row].imageUrl)
}
cell.recipeLabel.text = recipes[indexPath.row].title
}
//recipeTable.reloadData()
return cell
}
// structs for json
struct Root : Decodable {
let count : Int
let recipes : [Recipe]
}
struct Recipe : Decodable { // It's highly recommended to declare Recipe in singular form
let recipeId : String
let imageUrl, sourceUrl, f2fUrl : String
let title : String?
let publisher : String
let socialRank : Double
let page : Int?
let ingredients : [String]?
}
//recipes is array of Recipes
var recipes = [Recipe]() // array of recipes
//unfiltered recipes to put into search
fileprivate func getRecipes() {
let jsonURL = "https://www.food2fork.com/api/search?key=264045e3ff7b84ee346eb20e1642d9d9"
//.data(using: .utf8)!
guard let url = URL(string: jsonURL) else{return}
URLSession.shared.dataTask(with: url) {(data, response , err) in
if let response = response as? HTTPURLResponse, response.statusCode != 200 {
print(response.statusCode)
return
}
DispatchQueue.main.async {
if let err = err{
print("failed to get data from URL",err)
return
}
guard let data = data else{return}
//print(String(data: data, encoding: .utf8))
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
self.recipes = result.recipes
//print(result.recipes)
self.recipeTable.reloadData()
}catch let jsonERR {
print("Failed to decode",jsonERR)
}
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
//recipeTable.reloadData()
//search bar
//filteredRecipes = recipes
//call json object
getRecipes()
}
}
You could take this approach:
Add a Boolean variable to indicate whether searching or not
var searching: Bool = false
Use this for numberOfRowsInSection for the tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredRecipes.count
} else {
return recipes.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RecipeTableViewCell
var recipe: Recipe
if searching {
recipe = filteredRecipes[indexPath.row]
} else {
recipe = recipes[indexPath.row]
}
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(recipe.imageUrl)
}
cell.recipeLabel.text = recipe.title
return cell
}
And add this for the searchBar (set searching for other funcs like searchBarCancelButtonClicked)
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
filteredRecipes.removeAll()
view.endEditing(true)
} else {
searching = true
filteredRecipes = recipes.filter{$0.title.contains(searchBar.text!)}
}
tableView.reloadData()
}

Storing String and Int in a dictionary in swift

I'm almost new to Swift. In this URL I'll get some element; one of elements is categoryList which has two elements itself. I set the goodTypeName as the table's cell title, and when a cell is pressed it needs to send the goodType which is number (Int) to be placed in the next Url. I tried to create a dictionary but I failed!
UiTable code :::
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Global.GlobalVariable.names.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if Global.GlobalVariable.names == []
{
self.DisplayMessage(UserMessage: "nothing is available ")
print("server is nil")
}
let cell = UITableViewCell()
let content = Global.GlobalVariable.names[indexPath.row]
cell.textLabel?.text = content
cell.accessoryType = .disclosureIndicator
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.item)
let next = self.storyboard?.instantiateViewController(withIdentifier: "SVC")
self.navigationController?.pushViewController(next!, animated: true)
}
My problem is not with populating them in a table, my problem is when a cell is selected , goodtype is needed to be sent to next page, becuase next page's url has to have the goodtype code.
You can use the "prepareSegue" to pass Data.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "something"{
if let startViewController = segue.destination as? StartViewController{
startViewController.goodtype = Global.GlobalVariable.goodtype[indexPath.row]
}
}
}
And in your StartViewController just assign a variable to receive your data :
var goodtype = String()
Or use the navigation controller but with this line you can access to the another view controller property.
if let startViewController = storyboard.instantiateViewController(withIdentifier: "StartViewController") as? StartViewController {
startViewController.goodtype = Global.GlobalVariable.goodtype[indexPath.row]
let navigationController = UINavigationController()
navigationController.pushViewController(startViewController, animated: true)
}

My textlabel and detailtextLabel display the same text

Iphone screenshot
https://i.stack.imgur.com/zQ06M.png
I'm trying to make it so that my textLabel and my detailTextLabel show two different things but for some reason they both say the same thing. There should be just one cell here with the top label displaying the number 2 and the bottom label displaying the ;slakdfj(random typing for now). Refer to the links above for screenshot.
here is the main vc:
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
class ListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableViewProducts: UITableView!
var delegate: ListViewController?
var ref:DatabaseReference?
var databaseHandle: DatabaseHandle?
var postData = [productsList]()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference().child("0")
loadProducts()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postData.count
}
func loadProducts() {
ref?.observe(DataEventType.value, with: { (snapshot) in
var newSweets = [productsList]()
for post in snapshot.children {
let postObject = productsList(snapshot: post as! DataSnapshot)
newSweets.append(postObject)
print(self.postData)
}
self.postData = newSweets
self.tableViewProducts.reloadData()
}) { (error:Error) in
print(error)
}
}
//This places the text on the ViewControllerTableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let sweet = postData[indexPath.row]
cell.textLabel?.text = sweet.id
cell.detailTextLabel?.text = sweet.p_name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
}
here is the structure:
import Foundation
import UIKit
import FirebaseDatabase
struct productsList {
let id: String!
let p_name: String!
init(id: String, p_name: String) {
self.id = id
self.p_name = p_name
}
init (snapshot:DataSnapshot) {
id = snapshot.value as! String
p_name = snapshot.value as! String
}
}
Data Structure(is pretty basic for now):
[ {
"id" : "2",
"p_name" : ";slakdfj"
} ]
Here you set the same value into id and p_name.
That's the reason.
init (snapshot:DataSnapshot) {
id = snapshot.value as! String
p_name = snapshot.value as! String
}
You need to change this code something like this:
init (snapshot:DataSnapshot) {
var dict = snapshot.value as! [String: AnyObject]
id = dict["id"] as! String
p_name = dict["p_name"] as! String
}
Here you need to change "id" and "p_name" to fit your firebase database.

Populate UITableview with Array from Rest API in Swift

In my mainTableView Controller, I can print the API result in the console but I have no idea how I can set these to be visible in the cells of my tableview
import UIKit
//from: https://github.com/Ramotion/folding-cell
class MainTableViewController: UITableViewController {
let kCloseCellHeight: CGFloat = 179
let kOpenCellHeight: CGFloat = 488
let kRowsCount = 100
var cellHeights = [CGFloat]()
var restApi = RestApiManager()
var items: NSDictionary = [:]
override func viewDidLoad() {
super.viewDidLoad()
restApi.makeCall() { responseObject, error in
// use responseObject and error here
// self.json = JSON(responseObject!)
print("print the json data from api ")
self.items = NSDictionary(dictionary: responseObject!)
self.tableView.reloadData()
print(responseObject!.count)
// print(self.items)
let resultList = self.items["result"] as! [[String:
AnyObject]]
print(resultList[5])
}
createCellHeightsArray()
self.tableView.backgroundColor = UIColor(patternImage:
UIImage(named: "background")!)
}
// MARK: configure
func createCellHeightsArray() {
for _ in 0...kRowsCount {
cellHeights.append(kCloseCellHeight)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
guard case let cell as DemoCell = cell else {
return
}
cell.backgroundColor = UIColor.clearColor()
if cellHeights[indexPath.row] == kCloseCellHeight {
cell.selectedAnimation(false, animated: false, completion:nil)
} else {
cell.selectedAnimation(true, animated: false, completion: nil)
}
cell.number = indexPath.row
}
// with as! the cell is set to the custom cell class: DemoCell
// afterwards all data can be loaded in this method
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("FoldingCell",
forIndexPath: indexPath) as! DemoCell
//TODO: set all custom cell properties here (retrieve JSON and set in
cell), use indexPath.row as arraypointer
// let resultList = self.items["result"] as! [[String: AnyObject]]
// let itemForThisRow = resultList[indexPath.row]
// cell.schoolIntroText.text = itemForThisRow["name"] as! String
cell.schoolIntroText.text = "We from xx University..."
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeights[indexPath.row]
}
// MARK: Table vie delegate
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as!
FoldingCell
if cell.isAnimating() {
return
}
var duration = 0.0
if cellHeights[indexPath.row] == kCloseCellHeight { // open cell
cellHeights[indexPath.row] = kOpenCellHeight
cell.selectedAnimation(true, animated: true, completion: nil)
duration = 0.5
} else {// close cell
cellHeights[indexPath.row] = kCloseCellHeight
cell.selectedAnimation(false, animated: true, completion:
nil)
duration = 0.8
}
UIView.animateWithDuration(duration, delay: 0, options:
.CurveEaseOut, animations: { () -> Void in
tableView.beginUpdates()
tableView.endUpdates()
}, completion: nil)
}
}
I get this result in JSON which is correct
{
result = (
{
city = Perth;
"cou_id" = AU;
environment = R;
image = "-";
name = "Phoenix English";
rating = 0;
"sco_id" = 2;
"sco_type" = LS;
},
{
city = "Perth ";
"cou_id" = AU;
environment = L;
image = "-";
name = "Milner college";
rating = 0;
"sco_id" = 3;
"sco_type" = LS;
},
what do I have to do to set these values and set them here?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FoldingCell", forIndexPath: indexPath) as! DemoCell
//TODO: set all custom cell properties here (retrieve JSON and set in cell), use indexPath.row as arraypointer
cell.schoolIntroText.text = "We from xx University..."
return cell
}
I somehow dont figure out how to construct an array from this JSON output and how to access these fields which seem to be nested in many dimensions,
as a noob, thx for any inputs.
Addition from class restApi:
// working method for calling api
func makeCall(completionHandler: (NSDictionary?, NSError?) -> ()) {
Alamofire.request(
.GET,
baseURL+schools+nonAcademicParameter,
headers: accessToken
)
.responseJSON { response in
switch response.result {
case .Success(let value):
completionHandler(value as? NSDictionary, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
}
At your TODO point:
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
cell.cityLabel.text = itemForThisRow["city"] as! String
cell.nameLabel.text = itemForThisRow["name"] as! String
...
To make dealing with json easier in swift try SwiftyJSON.
I figured it out now. The size of the inner resultList array needs to be passed in numberOfRowsSelection. Then it loads the cell dynamically.
override func tableView(tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
print("size of self.resultlist.count: ")
print(self.resultList.count)
//size of the inner array with all the acutal values have to be 0
at the beginning. when the async call comes back with the whole
result, it will be updated. so it has to be set to the size of the
array
//http://www.unknownerror.org/opensource/Alamofire/Alamofire/q/stackoverflow/29728221/uitableview-got-rendered-before-data-from-api-returned-using-swiftyjson-and-alam
return self.resultList.count
}