I wonder if anyone can offer any guidance? I am writing an iPhone app, using Xcode 13.2.1. I am displaying a tableview within a scene that uses XIBs. It works fine. Above the table I have a header that is being displayed, it too works fine.
However, what I'd like to do is display the header, then display a cell that doesn't use the XIB (and that is a height of 50), and then displays every other cell after that first cell using a XIB (height is 195 - just an FYI). Thus, to do/implement this what I am trying to do is implement some kind of 'if statement' such that if indexPath.row is 0 then set the cell type to <call it cell type 1>, and if the indexPath.row is not 0 then set the cell type to <call it cell type 2>. I don't believe that I can use an IF statement because later in the code block it won't recognise the value of cell because it would have been set in an IF statement. Hence, I think I need to use a turnery operator, however I am struggling to construct the turnery operator.
The current code that sets up the cell for XIB in the
// MARK: TableView CELL Information
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Current code that sets up the cell for a XIB template
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
I KNOW THE FOLLOWING CODE DOESN'T WORK - however I am showing it this way to try and explain what I am trying to achieve:
// Intent is to use an IF or turnery operator to set the correct cell type
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
} else {
// Current code that sets up the cell for a XIB template
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
}
The follow on code (if I can somehow get the above to work) would mean that I would display some information in the first cell which would be <call it cell type 1> and then display other information in <call it cell type 2>.
Anyone done this before or would have any guidance on how to create such a turnery operator? I have tried many things but can't seem to manage to find the solution.
Cheers James.
Actually this was much easier to solve than trying to add complexity of turnery operators to determine which cell to dequeue. It was simply a case of using an IF and adding in the code I wanted to execute along with ensuring I put a return cell statement in it, meaning that if the IF-statement wasn't executed then the code executes the other dequeue statement... Thus, the code looks like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < 1 {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Select/tap on an event record below to edit the details of the journey."
cell.textLabel?.textAlignment = .center
cell.textLabel?.numberOfLines = 0
cell.backgroundColor = .orange
return cell
}
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
This gave me the result I needed. Also, the feedback from Shawn Frank above helped me realise I hadn't registered the first cell type (only the second one), thus when I registered both the first and second cell types within the class it all worked beautifully. Thank you to all who looked at the question and the fine folks above who gave guidance. Cheers James.
Related
I am trying to achieve a layout like this
My tableview has 3 cells (SubItemsCell (main cell), SubItemsListCell(n number of products inside main cell) and noSubstituteCell (fixed cell after the second cell count 1))
SubItemsCell has a "SELECT" Button that will expand and show SubItemsListCell, this cell will load as (dynamic) any count of products under the main SubItems Cell.
NoSubstitute Cell comes after the n number of products loads.
What my expected result is first load main cell that is self.archivedProducts and when u click each archived product's select button it expand and load self.newproducts and nosubstitute static cell. So main cell paired up with those 2 cells and show at once when we expand only.
So far I just loaded Main Cell only. I have no idea what should I do next, please give me an idea with a code or very similar example.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SubItemsCell", for: indexPath) as? SubItemsCell else {
fatalError ("SubstituteItems Cell not found!")
}
let product = self.archivedProducts[indexPath.row]
cell.titleLabel.text = product.title ?? ""
cell.priceLabel.text = "AED \(String(format: "%.2f", product.price ?? 0.00))"
cell.scaleLabel.text = product.uom ?? ""
cell.unavailableLabel.isHidden = false
cell.selectBtn.isHidden = false
let imageUrl = product.image?.url
let escapedUrl = imageUrl?.replacingOccurrences(of: "\\", with: "")
let replacedUrl = "\(escapedUrl ?? "")"
let url = URL(string: replacedUrl)
let plImage = UIImage(named: "whiteBg")
DispatchQueue.main.async {
cell.thumbImage.kf.setImage(with: url, placeholder: plImage, options: [.transition(.fade(0.2))])
}
}
I don't think you need a nested tableview. You can achieve the above with other elements. In order to achieve the layout above, my suggestion is the following:
Subclass your UITableViewCell into at least 2 different formats. There's a lot of ways to do this, my suggestion is to use a CocoaTouch Class w/ XIB (tutorial here) so you can be meticulous with the layout and constraints:
a. CellWDropdown (Cell #1 shown): You have a cell prototype that will include the image, the 3 labels in question and a button which act as the dropdown and will invoke a UIPickerView.
b. CellWStepper (Cells #2 and #3) : You'll have your image and 2 labels and what's called UIStepper for the +/- buttons
c. LastCell (Cell #4 if that's a cell? It could also be a view): likewise create the needed UI elements and constrain them appropriately.
Register the nib file you created:
tableView.register(UINib(nibName: "CellWDropDown", bundle: nil), forCellReuseIdentifier: "cellWDropdownIdentifier")
In cellForRowAt method, you invoke the XIBs created above to make the cell views based on the index OR desired cell type for example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellWDropdownIdentifier, for: indexPath) as! CellWDropdown
}
else if(indexPath.row == 1 || indexPath.row == 2){
let cell = tableView.dequeueReusableCell(withIdentifier: cellWStepperIdentifier, for: indexPath) as! CellWStepper
}
//do other actions/styling
}
Hook up delegate relationships/methods between the nibs you created and the UITableViewController. This is going to application specific.
There's a lot of detail that will have to fill in the gaps but this should put a major dent in getting started
I am having an issue with a UITableView Cell: "unable to dequeue a cell with identifier textCell", which I know is a common error, and I have read several of the entries here. The odd thing is, it works when I first draw the table. It does not fail until the user taps the search bar. I have checked the data at when the cell is being dequeued, and the various variables are populated, so the data is there.
I am using storyboards, so I should not need to register the cell, which is one of the common answers I have seen. Again, this is supported by the fact that the cell draw correctly at first. I was originally using dequeueReusableCell(withIdentifier:), but I also tried dequeueReusableCell(withIdentifier:for:), to no avail.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let ident = idList[indexPath.row]
var entryText = db.getText(ident)
let entryDate = db.getDateTimeLong(ident)
let comments = db.getComments(ident)
...
guard let cell = tableView.dequeueReusableCell(withIdentifier: "textCell", for: indexPath) as? TextTableViewCell else {
fatalError("NO SUCH CELL")
}
cell.dateLabel.text = entryDate
cell.textView.text = entryText
return cell
}
func updateSearchResults(for searchController: UISearchController) {
idList = db.textSearch(searchController.searchBar.text ?? "")
tableView.reloadData()
}
Any idea where I am going wrong?
I'm working through an exercise which uses tableviews. I noticed within a test during the exercise, they use a method I haven't needed in the past when implementing tableviews from storyboards. The method is:
func register(AnyClass?, forCellReuseIdentifier: String)
After reading the short description of this function in the reference pages. I'm curious to know what does apple mean by term "registers"? I half assume that since we are doing this exercise programmatically at the moment, this function is only needed if you're creating UITableviews programmatically. If this statement is incorrect, please let me know as I'd like to learn more.
Here is the code from the example:
func test_CellForRow_DequesCellFromTableView(){
let mockTableView = MockTableView()
mockTableView.dataSource = sut
mockTableView.delegate = sut
mockTableView.register(ItemCell.self, forCellReuseIdentifier: "ItemCell")
sut?.itemManger?.add(ToDoItem.init(title: "Foo"))
mockTableView.reloadData()
_ = mockTableView.cellForRow(at: IndexPath.init(row: 0, section: 0))
XCTAssertTrue(mockTableView.cellGotDequeed)
}
The DequeueReusable methods are there to check if any reusable cells are left before creating new ones. Hope you have an idea about the working of reusable cells
What happens when the queue is empty? Now we do need to create a cell. We can follow 2 methods to create a cell,
Create cell manually
Create it automatically by registering cell with a valid xib file
METHOD 1
if you do it with manually, you must check cell is empty or not after dequeueReusableCell check. Just like below,
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Reuse an old cell if exist else return nil
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
//check cell is nil if nil you want to allocate it with proper cell
if(cell == nil){
//create cell manually
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "CellSubtitle")
}
// do stuff to the cell here
return cell
}
METHOD 2
We could create the cell manually like above which is totally fine. But it would be convenient if the table view would create the cell for us directly.
That way we don't have to load it from a nib or instantiate it.
For registering a cell with a xib or class we use func register(AnyClass?, forCellReuseIdentifier: String) method. Let see an example,
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(MyCell.self, forCellReuseIdentifier: "Cell")
}
// ...
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) as MyCell
// no "if" - the cell is guaranteed to exist
// ... do stuff to the cell here ...
cell.textLabel.text = // ... whatever
// ...
return cell
}
You are "registering" your custom Cell class - ItemCell - for reuse as a cell for your tableview.
See: https://developer.apple.com/reference/uikit/uitableview/1614888-register
"Register" tells XCode that the cell exists. A cell is registered under a "reuse identifier." This is a unique string that corresponds to your TableViewCell, in this case ItemCell.
A cell can also be registered in the Storyboard by filling out the "Identifier" in the cell's attributes inspector.
I am trying to build an app with 2 cell on a tableview
the header that will have the Headlines and the second that will have the normal feeds.
My question is how to connect this two cells
The code for the raw feeds are working
Example:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
// Configure the cell...
let post = self.posts[indexPath.row]
cell.title.text = post.title?.utf8Data?.attributedString?.string
if let imageUrl = post.imageUrl {
cell.imgView?.downloadImage(from: imageUrl)
}
let myFormatter = DateFormatter()
myFormatter.dateStyle = .none
myFormatter.timeStyle = .short
cell.pubDate.text = myFormatter.string(from: post.pubDate!) // What gives?
return cell
}
}
You can do this by filling a solid color, like white, for both the cells and set Table View separator to none.
tableView.separatorStyle = .none
And if you want to create a serious of such combined cells, then add a view to the bottom of the second cell, i.e. Feed Row, and fill that view with other color, like grey. This view will work as a separator for serious.
I suggest you let your tableview empty in storyboard. (delete all cells)
What I usually do, and what I feel is more flexible and cleaner in terms of code and files, is always use custom cells.
Create a subclass of UITableViewCell for each cell type you need (here, 2). One could be HeaderCell, and the other could be FeedCell.
If you use the "Create UITableViewCell class" I think you can find in Xcode (haven't used it in a long time), it should create the class for you, as well as a .xib file. If you don't have the corresponding .xib just create it and link it manually.
The .xib file is where you will put your labels, connect the outlets and maange everything you need. That's where you'll remake the cells you deleted ealier on. I think you can actually cut and paste in the xib and it might work.
Now you have a custom class, with its corresponding xib, which you can use as a cell.
Make sure your tableview is connected as delegate and datasource in storyboard (it should already be done in your setup).
In ViewDidLoad you will need to register all your cell classes to the tableview.
That is done using this method call on your tableview property (outlet)
I'm not sure how to write it in swift, but it's
MyTableView.Register(nib, key) in pseudo code.
Both the parameters come from your custom cell class (it's the .Nib and the class name, which you could hardcode). I usually do MyHeaderCell.Nib and MyHeaderCell.Key that are put in static, but that you can do however you want.
Now that your tableview is aware of the cells it will have to display, you just have to manage it in your cellForRow method.
Simply do it by index :
var cell;
if (indexPath.Row == 0)
{
cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
}
else
{
cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as! FeedCell
}
let post = self.posts[indexPath.row]
cell.title.text = post.title
return cell
And there you go :)
I've never done swift so there are certainly syntax mistakes but the idea is how you should do it.
I have setup a segue that will show a view controller with a small TableView. I want a different segue to show a bigger TableView but I want the bigger table to have the same exact info as the smaller table. Got the smaller tableView working perfect on its own, but once I give the bigger table a Data source, reset and try it out.....crashes.
// IndexPath or First Cell in TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if self.TaskTableViews.hidden == false {
cell = tableView.dequeueReusableCellWithIdentifier( "FirstTask" , forIndexPath: indexPath) as UITableViewCell!
let list = frc.objectAtIndexPath(indexPath) as! List
cell.textLabel?.text = list.taskName
cell.textLabel?.textColor = UIColor.whiteColor()
TaskTableViews.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
TaskTableViews.layer.cornerRadius = 8
TaskTableViews.separatorColor?.colorWithAlphaComponent(2.0) }
if self.TaskTable2.hidden == false {
cell = tableView.dequeueReusableCellWithIdentifier( "Second Task" , forIndexPath: indexPath) as UITableViewCell!
let list = frc.objectAtIndexPath(indexPath) as! List
cell.textLabel?.text = list.taskName
cell.textLabel?.textColor = UIColor.whiteColor()
TaskTable2.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
TaskTable2.layer.cornerRadius = 8
TaskTable2.separatorColor?.colorWithAlphaComponent(2.0) }
return cell as UITableViewCell
}
The problem is that the code for your two tables is slamming into each other. To fix this, rejigger your logic. Do not make your logic depend on what is hidden. You can handle only one table at a time; just one table is calling here. That table comes in as the tableView parameter. Make your logic depend on that. Depending what table view that tableView parameter is, configure the cell and return it for that table view.
I think that you should consider changing your approach by just resizing the table view dynamically when you go from one scene to the other instead of having two table views if the information is exactly the same.
If you're still pushing for this approach then don't make the condition be that the table view is hidden and instead implement your own logic or boolean to determine this. But again, I'd rather resize a single table view as needed.
You have many problems in your code
1.
var cell = UITableViewCell()
What's the point of this line?
2.
cell = tableView.dequeueReusableCellWithIdentifier( "FirstTask" , forIndexPath: indexPath) as UITableViewCell!
What's the point of casting? This function returns UITableViewCell (not even optional)
3.
cell.textLabel?.text = list.taskName
Should not compile, cause UITableViewCell doesn't have textLabel
4.
TaskTableViews.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
What's the point of doing this every cell request? Move this to viewDidLoad or other appropriate place
Your if { } parts are identical except of reuse identifier
Use tableView argument when needed
Use if { } else
8.
return cell as UITableViewCell
Why cast? Just return
I'm sure I haven't found all of them))