Make tableView cell with struct and data by indexpath - ios

I already known how to make a basic tableView, below is my basic code
class ShoppingTableViewController: UITableViewController{
var description = ["D1", "299900", "D2", "P201712310000003000", "D3", "ASS+DfDFxSuu", "D10", "901", "D11", "00,46246226301561000110001001", "D12", "20201231123030"]
var dictDescription = ["D10": "901", "D11": "00,46246226301561000110001001", "D3": "ASS+DfDFxSuu", "D12": "20201231123030", "D2": "P201712310000003000", "D1": "299900"]
override func viewDidLoad() {
super.viewDidLoad()
// Xib
tableView.register(UINib(nibName:PropertyKeys.pictureCell , bundle: nil), forCellReuseIdentifier: PropertyKeys.pictureCell)
tableView.register(UINib(nibName:PropertyKeys.infoCell , bundle: nil), forCellReuseIdentifier: PropertyKeys.infoCell)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.pictureCell, for: indexPath) as! PictureWithTableViewCell
cell.iconImageView.image = UIImage(named: "amount")
cell.lbDescription.text = "Type"
cell.lbName.text = "Shopping"
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.lbDescription.text = "Taiwan dollar"
cell.lbName.text = m_dictDescription["D1"]
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.pictureCell, for: indexPath) as! PictureWithTableViewCell
cell.iconImageView.image = UIImage(named: "info")
cell.lbDescription.text = "BankName"
cell.lbName.text = m_dictDescription["D11"]
return cell
case 3:
if m_dictDescription["D2"] != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.lbDescription.text = "orderNumber"
cell.lbName.text = m_dictDescription["D2"]
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.isHidden = true
return cell
}
but this is an unsafe way because i write func number of rows and cell for row as hardcode.
so I want to change tableView composing way from decide cell format first then fill data in (like my basic code) to let data decide my number of rows and cell for row. (use indexPath)
but I got some problems:
I try to write:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let test = self.m_arrDescription[indexPath.row]
cell.lbName.text = test
It works but every cell looks the same while I want to present different cells.
I search some information on internet, perhaps I can use struct and combine m_arrDescription to make tableview cell.
// use struct to decide cell label or image , for example: cell.lbDescription.text ...
struct CellFormat {
var title : String
var image : UIImage
var name : String
}
So far this is what i've written, can anyone please kindly help me to go on?
Be clearly, how do I use [indexPath.row] in this code ?

please create first one enum
enum CellType: String, CaseIterable {
case title, image, name
}
and then use this code in tableview delegate methods.
switch CellType(rawValue: indexPath.row) {
case .title:
break
case .image:
break
case .name:
break
case .none:
break
}
If any problem let me know

Related

ExpandableCell cell reuse issues

I'm trying to use the library ExpandableCell to add collapsable table view cells to my app. I'm using the latest version of the library which is 1.3.0.
Below is the full code.
import UIKit
import ExpandableCell
class ViewController: UIViewController {
#IBOutlet weak var tableView: ExpandableTableView!
private var passengers = [Passenger]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: .zero)
tableView.expandableDelegate = self
passengers = [
Passenger(id: 1, name: "Mark", trips: [Trip(id: 1, route: "NY to NJ")]),
Passenger(id: 2, name: "Jesica", trips: [Trip(id: 1, route: "NY to NJ"), Trip(id: 2, route: "LA to LV")]),
Passenger(id: 3, name: "Brian", trips: [Trip(id: 2, route: "Kansas City to Denver")])
]
tableView.reloadData()
}
}
extension ViewController: ExpandableDelegate {
func expandableTableView(_ expandableTableView: ExpandableTableView, numberOfRowsInSection section: Int) -> Int {
return passengers.count
}
func expandableTableView(_ expandableTableView: ExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PassengerCell.reuseIdentifier, for: indexPath) as! PassengerCell
let passenger = passengers[indexPath.row]
cell.textLabel?.text = passenger.name
cell.detailTextLabel?.text = "\(passenger.trips?.count ?? 0) trips"
return cell
}
func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
let passenger = passengers[indexPath.row]
if let trips = passenger.trips {
var cells = [TripCell]()
for trip in trips {
cell.textLabel?.text = trip.route
cells.append(cell)
}
return cells
} else {
return nil
}
}
func expandableTableView(_ expandableTableView: ExpandableTableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func expandableTableView(_ expandableTableView: ExpandableTableView, heightsForExpandedRowAt indexPath: IndexPath) -> [CGFloat]? {
let count = passengers[indexPath.row].trips?.count ?? 0
let heightArray = [CGFloat](repeating: 50, count: count)
return heightArray
}
func expandableTableView(_ expandableTableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
return true
}
}
The data is loaded correctly and the tableview appears as expected. But the problem is when you tap on a collapsed cell. It acts...weird.
Notice how some cells don't appear at all (the second group should show 2 yellow cells). And some cells appear in other groups that they don't belong in. It looks like a cell reuse issue.
I tried overriding the prepareForReuse method and reset the controls manually as well but that didn't work either.
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
backgroundColor = nil
}
I saw some similar issues in the library's Github repo but there aren't any answers or fixes.
If anyone has used this library before, any idea what might be causing this issue and how to fix it?
Demo project
Looking at your Demo Project...
In expandedCellsForRowAt in ViewController, you are creating one cell object, then assigning it different text values and appending it to an array.
func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
// here, you create a cell object
let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
let passenger = passengers[indexPath.row]
if let trips = passenger.trips {
var cells = [TripCell]()
for trip in trips {
// here, you repeatedly set the text of the SAME cell object
cell.textLabel?.text = trip.route
cells.append(cell)
}
return cells
} else {
return nil
}
}
Use this instead:
func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
// Don't create the cell here
//let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
let passenger = passengers[indexPath.row]
if let trips = passenger.trips {
var cells = [TripCell]()
for trip in trips {
// create a NEW cell for each trip (don't use indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier) as! TripCell
cell.textLabel?.text = trip.route
cells.append(cell)
}
return cells
} else {
return nil
}
}

Implementing static table view cells with data in an array

I have some data which is pulled from a DB and stored in an array within my UITableViewController. However, when I try to put the data inside each of my cells, I get an error that my index is out of the bounds of the array.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellItems.count
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item: ItemPreBasketModel = cellItems[indexPath.row] as! ItemPreBasketModel
if indexPath.row == 0 {
//Desc cell
let cellIdentifier: String = "descCell"
let descCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
return descCell
} else if indexPath.row == 1 {
//Price cell
let cellIdentifier: String = "priceCell"
let priceCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
return priceCell
} else if indexPath.row == 2 {
//Quantity cell
let cellIdentifier: String = "quantityCell"
let quantityCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
return quantityCell
} else {
//Submit cell
let cellIdentifier: String = "submitCell"
let submitCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
return submitCell
}
}
Here is the cellItems variable and also where it is populated:
var cellItems: NSArray = NSArray() {
didSet {
tableView.reloadData()
}
}
func itemsDownloaded(items: NSArray) {
cellItems = items
tableView.reloadData()
}
Here is the static table view in the storyboard:
What I want to do, which I've took out of the code for as it still fails before reaching this part, is to use the 'item' variable I declare inside cellForRowAt and populate each of my cell outlets with a part of the object,
i.e: descCell.descLabel.text = item.name
The error I get is:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
on the line
let descCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
You should not use a table view controller with static cells and cellForRowAt at the same time. You should use dynamic prototypes to populate an array dynamically.
To change the content type of your table view, select it in Interface Bulder, open the Attributes inspector (the fourth icon from the left in the right sidebar) and select Dyanmic Prototypes for Content under the Table View section.

How to display dynamically data from Server in CollectionViewCell in TableViewCell with swift3?

I got my json link data from TableViewCell , and then retrieve that data from server and display in collectionView with related TableViewCell data.
How to display this data in swift3? Please, help me.
I got url link (mainThemeList.main_associated_url,main_name) from TableViewCell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let mainThemeList = mainHomeThemeTable[(indexPath as NSIndexPath).row]
let cell = tableView.dequeueReusableCell(withIdentifier: "homecell") as! HomeCategoryRowCell
DispatchQueue.main.async {
cell.categoryTitle.text = mainThemeList.main_name
cell.mainAssociatedURL.text = mainThemeList.main_associated_url
self.prefs.set(mainThemeList.main_name, forKey: "main_name")
cell.categoryTitle.font = UIFont.boldSystemFont(ofSize: 17.0)
cell.collectionView.reloadData()
}
DispatchQueue.main.async {
self.retrieveDataFromServer(associated_url: mainThemeList.main_associated_url,main_name: mainThemeList.main_name)
}
return cell
}
And then data related url link data from Server.
private func retrieveDataFromServe(associated_url : String , main_name: String) {
SwiftLoading().showLoading()
if Reachability().isInternetAvailable() == true {
self.rest.auth(auth: prefs.value(forKey: "access_token") as! String!)
rest.get(url: StringResource().mainURL + associated_url , parma: [ "show_min": "true" ], finished: {(result : NSDictionary, status : Int) -> Void in
self.assetsTable.removeAll()
if(status == 200){
let data = result["data"] as! NSArray
if (data.count>0){
for item in 0...(data.count) - 1 {
let themes : AnyObject = data[item] as AnyObject
let created = themes["created"] as! String
let assets_id = themes["id"] as! Int
let name = themes["name"] as! String
var poster_img_url = themes["poster_image_url"] as! String
let provider_id = themes["provider_id"] as! Int
poster_img_url = StringResource().posterURL + poster_img_url
self.assetsTable.append(AssetsTableItem(main_name: main_name,created: created,assets_id: assets_id, name: name, poster_image_url: poster_img_url,provider_id: provider_id))
}
}
SwiftLoading().hideLoading()
}else{
SwiftLoading().hideLoading()
}
})
}
}
Retrieve data from Server data store in assetsTable.
And then assetsTable data display in CollectionViewCell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! HomeVideoCell
cell.movieTitle.text = list.name
cell.imageView.image = list.image
return cell
}
My problem is collectionViewCell data are duplicate with previous assetsTable data and didn't show correct data in CollectionView.
My tableViewCell show (Action, Drama) label and My CollectionViewCell show movies Name and Movie Image. I retrieve data for CollectionViewCell from server but CollectionViewCell didn't display related data.
in HomeVideoCell Subclass clean up data in prepareforreuse
override func prepareForReuse() {
super.prepareForReuse()
self.movieTitle.text = ""
self.imageView.image = nil
}

Break error while creating a calendar

Have break error Thread 1: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode==0x0). No errors with build, just when run, have a break
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let calendars = self.calendars {
return calendars.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
//error happens here
if self.calendars != nil {
let calendarName = self.calendars?[(indexPath as NSIndexPath).row].title
cell.textLabel?.text = calendarName
} else {
cell.textLabel?.text = "Unknown Calendar Name"
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! UINavigationController
let addCalendarVC = destinationVC.viewControllers[0] as! AddCalendarViewController
addCalendarVC.delegate = self
}
func calendarDidAdd() {
self.loadCalendars()
self.refreshTableView()
}
}
tableView.dequeueReusableCell(withIdentifier: "Cell")!
You are unwrapping an optional value which might be nil in the first place. Cell might not have been created yet especially if you haven't registered the cell's class with that identifier so it'll crash first time table tries to populate the cell. You should first check if cell is nil:
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
...
The immediate red flag I see is here:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
When dequeueing reusable cells, I like to wrap them in guard statements, so my app doesn't crash. It also tells me a bit more information when something does go wrong:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
print("couldn't dequeue a reusable cell for identifier Cell in \(#function)")
return UITableViewCell()
}
This crash could be for a few reasons. You may have forgotten to register the reuse identifier, but if you're using storyboards this is handled for you. There may simply be a typo or you forgot to enter a reuse identifier for that cell.

How to animate UITableViewCell using Swift?

I have a UITableView and inside the tableViewCell I have a UICollectionView.
My requirement is while tapping on a button of first tableView cell I have to animate second tableViewCell.
Below is my code :-
//Cell For Row at indexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (0 == indexPath.section) {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstRowCell") as! FirstRowCell
cell.btnReview.addTarget(self, action: #selector(GotoReview), for: UIControlEvents.touchUpInside)
cell.btnMyProduct.addTarget(self, action: #selector(AnimateCollectionView), for: UIControlEvents.touchUpInside)
//cell.
return cell
} else if ( 1 == indexPath.section) {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
return tableCollectionCell!
} else {
let identifier = "BrandImagesTableCell"
var brandImagesTableCell = tableView.dequeueReusableCell(withIdentifier: identifier)
if(brandImagesTableCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("BrandImagesTableCell", owner: self, options: nil)!
brandImagesTableCell = nib[0] as? BrandImagesTableCell
}
//brandImagesTableCell.
return brandImagesTableCell!
}
}
In my code you can see:
if (0 == indexPath.section)
In that I have a button target (#selector(AnimateCollectionView)).
I want to animate tableCollectionCell which is at (1 == indexPath.section).
See my AnimateCollectionView method :-
func AnimateCollectionView() {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
tableCollectionCell?.alpha = 0
UIView.animate(withDuration: 1.50, animations: {
//self.view.layoutIfNeeded()
tableCollectionCell?.alpha = 1
})
}
If you want to animate its change, you can just change some state variables, call tableView.reloadRows(at:with:), and have your cellForRowAt then check those state variables to know what the final cell should look like (i.e. whether it is the "before" or "after" cell configuration).
For example, here is an example, with two cells, toggling the second cell from red to blue cells.
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "RedCell", bundle: nil), forCellReuseIdentifier: "RedCell")
tableView.register(UINib(nibName: "BlueCell", bundle: nil), forCellReuseIdentifier: "BlueCell")
}
#IBAction func didTapButton(_ sender: UIButton) {
isRedCell = !isRedCell
tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .fade)
}
var isRedCell = true
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath)
return cell
} else if indexPath.row == 1 {
if isRedCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RedCell", for: indexPath)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "BlueCell", for: indexPath)
return cell
}
}
fatalError("There are only two rows in this demo")
}
}
Now, assuming you really needed to use NIBs rather than cell prototypes, I would register the NIBs with the tableview like above, rather than having cellForRow have to manually instantiate them itself. Likewise, for my cell with the button, I just used a prototype cell in my storyboard and hooked the button directly to my #IBOutlet, avoiding a NIB for that cell entirely.
But all of that is unrelated to your main problem at hand: You can create your cells however you want. But hopefully this illustrates the basic idea, that on the tap of the button, I'm not trying to load any cells directly, but I just update some status variable that cellForRow will use to know which cell to load and then tell the tableView to animate the reloading of that IndexPath.
By the way, I assumed from your example that the two different potential cells for the second row required different NIBs. But if you didn't, it's even easier (use just one NIB and one cell identifier), but the idea is the same, just update your state variable and reload the second row with animation.

Resources