Create UITableView with multiple Custom Cells in Swift - json

What is the best current way to create an UITableView with multiples Custom Cell with the storyboard and the tableView methods?
For now, I get correctly my JSON response split into 3 arrays then I want to use it to update my tableView with 3 different custom cells.
class MainViewController: UIViewController {
// MARK: - Properties
var starters = [Starter]()
var dishes = [Dish]()
var deserts = [Desert]()
// MARK: - Outlets
#IBOutlet weak var foodTableView: UITableView!
// MARK: - Functions
func updatDisplay() {
ApiHelper.getFoods { starters, dishes, deserts in
self.starters = starters
self.dishes = dishes
self.deserts = deserts
self.foodTableView.reloadData()
}
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
updatDisplay()
}
}
extension MainViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StarterCell", for: indexPath)
return cell
}
}

Assuming that you have the three sections "starters", "dishes" and "deserts" you can display the cells like this:
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return starters.count
}
else if section == 1 {
return dishes.count
}
else {
return deserts.count
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Starters"
}
else if section == 1 {
return "Dishes"
}
else {
return "Deserts"
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
return tableView.dequeueReusableCell(withIdentifier: "StarterCell", for: indexPath)
}
else if indexPath.section == 1 {
return tableView.dequeueReusableCell(withIdentifier: "DishesCell", for: indexPath)
}
else {
return tableView.dequeueReusableCell(withIdentifier: "DesertsCell", for: indexPath)
}
}

Related

How to change clicked cell button label with JSON response in swift

In my tableview cell i added awerdedBtnLabel and awardedBtn.. if i tap button on any one cell then i need to change only tapped cell label text
code for tableview: with the below code if i click on any one cell awardedBtn then all cells awerdedBtnLabel text is changing, why?
how to change label text only on clicked cell, please do guide me
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewproposalData?.result?.bids?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewProposalTableVIewCell", for: indexPath) as! ViewProposalTableVIewCell
let bidData = viewproposalData?.result?.bids?[indexPath.row]
cell.propAmt.text = "$\(bidData?.amount ?? "")"
cell.awardedBtn.tag = indexPath.row
cell.awardedBtn.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
let postServData = viewproposalData?.result?.posted_services
if postServData?.is_awarded == "Y"{
cell.awerdedBtnLabel.text = "Rewoke Award"
cell.milestoneView.isHidden = false
}else{
cell.awerdedBtnLabel.text = "Award"
cell.milestoneView.isHidden = true
}
return cell
}
#objc func connected(sender: UIButton){
}
EDIT: according mentioned answer i have tried like below.. but nothing changing now.. i mean not all cells or not a single.. the button label text remains same.. if i tap on any button then no change in label
class ViewProposalTableVIewCell: UITableViewCell, UICollectionViewDelegate,UICollectionViewDataSource {
var onAwardBtn: ((_ isAwarded: String) -> Void)?
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewproposalData?.result?.bids?.count ?? 0//5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewProposalTableVIewCell", for: indexPath) as! ViewProposalTableVIewCell
let postServData = viewproposalData?.result?.posted_services
let isAwarded = postServData?.is_awarded
cell.onAwardBtn = { [weak self] (isAwarded) in
if isAwarded == postServData?.is_awarded {
cell.awerdedBtnLabel.text = "Rewoke Award"
cell.milestoneView.isHidden = false
}
else{
cell.awerdedBtnLabel.text = "Award"
cell.milestoneView.isHidden = true
}
}
2nd Edit:
JSON response from console:
JSON {
jsonrpc = "2.0";
result = {
bids = (
{
amount = 100;
id = 153;
"get_bid_user" = {
address = dfsdffafadf;
};
},
);
"posted_services" = {
"is_awarded" = Y;
};
};
}

I can't print to TableView data with Swift JSON

I'm new to using JSON and wanted to start with a simple app to provide an overview of the movie. The following code does not print anything on the tableView, the app remains empty, with no results. He makes no mistakes. In the debug area, however, the data prints them to me. How can I get the results on the tableView?
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var popularMoviesArray = [Results]()
var swiftManager = SwiftManager()
var tableViewCell = TableViewCell()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var labelError: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
swiftManager.delegate = self
swiftManager.fetchUrl()
self.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return popularMoviesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = popularMoviesArray[indexPath.row]
cell.labelTitle.text = item.title
cell.labelYear.text = item.release_date
cell.labelRate.text = String(item.vote_average ?? 0.0)
cell.labelOreview.text = item.overview
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToDetail", sender: indexPath.row)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier != nil else {
return
}
let letRow = sender as? Int
switch segue.identifier {
case "goToDetail":
(segue.destination as! ViewControllerDetail).itemDetail = popularMoviesArray[letRow!]
default:
return
}
}
}
extension ViewController: SwiftManagerDelegate {
func didUpdateStruct(_ swiftManager: SwiftManager, swiftData: SwiftData) {
DispatchQueue.main.async {
self.popularMoviesArray = swiftData.results
print("PRINT ARRAY - \(self.popularMoviesArray)")
}
}
func didFailWithError(error: Error) {
print(error)
}
}
You have to reload the table view in the delegate method because the data is loaded asynchronously
func didUpdateStruct(_ swiftManager: SwiftManager, swiftData: SwiftData) {
DispatchQueue.main.async {
self.popularMoviesArray = swiftData.results
self.tableView.reloadData()
print("PRINT ARRAY - \(self.popularMoviesArray)")
}
}
Reloading the table view in viewDidLoad is pointless.

Trying to reference each item of custom type for tableView cells

I am parsing data from a JSON file and I've created a struct to hold that data. I am trying to display each item of the custom struct in a tableView, but I'm getting stuck on how I should reference each item.
Here's my struct:
struct Country: Codable {
var id: Int
var country: String
var capital: String
var nationalLanguage: [String]
var population: Int
}
And here is my table view controller. Right now I only know how to reference a single item in my custom type. This obviously sets all of the cells to that one item.
class TableViewController: UITableViewController {
var countryItem: Country?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
if let country = countryItem {
cell.textLabel!.text = String(country.population)
}
return cell
}
}
When I print out my countryItem variable, this is what I get:
Country(id: 1, country: "United States", capital: "Washington D.C.", nationalLanguage: ["English"], population: 328239523)
Do I need to somehow set that as an array so I can refer to each item individually?
UPDATED:
Option1
class TableViewController: UITableViewController {
var countryItem: Country?
var arrayStrings: [String] {
guard let countryItem = countryItem else { return [] }
return [
"\(countryItem.id)",
countryItem.country,
countryItem.capital,
countryItem.nationalLanguage.joined(separator: ", "),
"\(countryItem.population)",
]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayStrings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
cell.textLabel!.text = arrayStrings[indexPath.row]
return cell
}
}
Option2
class TableViewController: UITableViewController {
var countryItem: Country?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryItem == nil ? 0 : 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
let text: String
switch indexPath.row {
case 0:
text = "\(countryItem!.id)"
case 1:
text = countryItem!.country
case 2:
text = countryItem!.capital
case 3:
text = countryItem!.nationalLanguage.joined(separator: ", ")
case 4:
text = "\(countryItem!.population)"
default:
break
}
cell.textLabel!.text = text
return cell
}
}
If you all you want is just display string representation of all fields in your struct, then yes, convert them into a single array of string and use reusable cells to render.
If you need different styles for each field, then you may not need an array, just create some custom cells and then assign data for them from the struct.
Or maybe you don't even need a table view here since I see you have only one Country and no reusable needed here. Just create a custom view with 5 (maybe) labels and display your data.
For some reason, you still need a table view, then create a single cell that can display all information for your country.
Hope it can help you.
class TableViewController: UITableViewController {
var countryItem = [Country]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryItem.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
cell.textLabel!.text = countryItem[indexPath.row].country
return cell
}
}

how to fix tableview automatic height in swift?

I want to display vertical data collection view in table view cell. but when the data is first reloaded from json the height of the table view doesn't change automatically. but when the tableview is scrolled up, the height of the tableview changes as shown below
This first image appeared when reloading data from Json:
enter image description here
this image when tableview is scrolled up
enter image description here
here is myCode:
view controller
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, reloadTable {
func reloadTableData() {
self.tableView.reloadData()
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: Cell.tableView.rawValue)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.tableFooterView = UIView()
tableView.register(UINib(nibName: "SecondTableViewCell", bundle: nil), forCellReuseIdentifier: "SecondTableViewCell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "SecondTableViewCell", for: indexPath) as! SecondTableViewCell
cell.name.text = "first data"
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: Cell.tableView.rawValue, for: indexPath) as! TableViewCell
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0{
return 100
}else {
return UITableView.automaticDimension
}
}
}
here is my tableview cell:
TableViewCell
import UIKit
import Alamofire
import SwiftyJSON
struct dataJSON {
var name: String
}
protocol reloadTable {
func reloadTableData()
}
class TableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
var reload: reloadTable?
var dataJson : [dataJSON] = []
#IBOutlet var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
fetchData()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isScrollEnabled = false
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: Cell.collView.rawValue)
let collViewLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collViewLayout.itemSize = UICollectionViewFlowLayout.automaticSize
layoutIfNeeded()
setNeedsLayout()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataJson.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.collView.rawValue, for: indexPath) as! CollectionViewCell
cell.detail.text = dataJson[indexPath.row].name
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width / 2 - 10, height: 300)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)
}
func fetchData(){
Alamofire.request("https://jsonplaceholder.typicode.com/users", method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON{
(response) in
switch response.result{
case .success(let value):
print(value)
let json = JSON(value)
let name = json["name"].stringValue
print("nameesss: \(name)")
json.array?.forEach({ (item) in
let data = item["name"].stringValue
self.dataJson.append(dataJSON(name: data))
})
self.collectionView.reloadData()
self.reload?.reloadTableData()
case .failure(let error):
print(error)
}
}
}
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: 600)
self.collectionView.layoutIfNeeded()
return self.collectionView.collectionViewLayout.collectionViewContentSize
}
}
Try with
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
After inserting data you must update subviews with self.view.layoutSubviews().
Make the delegate and datasource connections from the connection inspector.
Click and drag the outlets from connection inspector to File owner for datasource and delegate.

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)
}