I am trying to let the user infinitely scroll the screen for more fibonacci numbers. I can only display up to the number of fib numbers that is the length of my posts array. Here is what it looks like now. I am having trouble implementing the scrollViewDidScroll function to achieve the infinite scroll. I found some code on stackoverflow that makes sense to me, but I don't know what to do to connect it to the tableview (the part where you call for more data). Any input is appreciated!
import UIKit
class FeedTableViewController: UITableViewController {
let posts : [String] = ["","","","","","","",""]
var fibArray: [Int] = [0,1]
let cellIdentifier = "userPostFeedCell"
var indexOfPageToRequest = 1
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
// increments the number of the page to request
indexOfPageToRequest += 1
// call your API for more data
// tell the table view to reload with the new data
self.tableView.reloadData()
}
}
func fibonacci(n: Int) -> Int {
if (fibArray.count > n) {
return fibArray[n];
}
let fibonacciNumber = fibonacci(n: n - 1) + fibonacci(n: n - 2)
fibArray.append(fibonacciNumber)
return fibonacciNumber
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(
UINib(nibName: "FeedTableViewCell", bundle: nil), forCellReuseIdentifier: cellIdentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> FeedTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.text = "\(fibonacci(n: indexPath.row+1))"
print("cellForRowAt \(indexPath.row)")
return cell as! FeedTableViewCell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("heightForRowAt \(indexPath.row)")
return 40
}
}
You'll overflow an Int pretty quick in a fibonacci series so you don't actually need infinite scroll. You just set the number of rows in your section to be high. The application only ever creates the cells that need to be displayed so you won't be using large amount of memory. Here is my code
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20000
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.text = "\(fibonacci(n: indexPath.row+1))"
return cell
}
Related
I have fix make the cell to cliptobounds in the table view and also assign constraints to fix the table position and height.
Below are some parts of my code.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if hiddenRow.contains(indexPath.row) || hiddenRow2.contains(indexPath.row){
rowHeight.append(300)
return 300 //Expanded
}
else{
rowHeight.append(120)
return 120 //Not Expanded
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "med_reusable_cell", for: indexPath as IndexPath) as! MedListTableViewCell
cell.backgroundColor = TRANSPARENT
cell.layer.cornerRadius = DEFAULT_CORNER_RADIUS
active_table_height.constant = self.view.frame.size.height * 11/36
expired_table_height.constant = self.view.frame.size.height * 11/36
cell overflow
The different between my code and others are
This is an expendable view cell which the height will be change based whether the cell is expended
I use a reusable cell for two tables.
How can I solve this?
You can achieve this by adding a header to each cell, then when you'll click it, reload the table view with the opened cell look at this example :
DataModel :
struct DataItem {
var isExpand: Bool
var title: String
var value:String
init(isExpand:Bool = false, title:String, value:String) {
self.isExpand = isExpand
self.title = title
self.value = value
}
}
Custom Header witch will listen to events :
protocol CustomHeaderViewDelegate: AnyObject {
func headerViewTap(_ section: Int)
}
class CustomHeaderView: UITableViewHeaderFooterView {
weak var delegate: CustomHeaderViewDelegate?
var sectionNumber: Int?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let gesture = UITapGestureRecognizer(target: self, action: #selector(CustomHeaderView.tableViewSectionTapped(_:)))
self.addGestureRecognizer(gesture)
}
#objc func tableViewSectionTapped(_ gesture: UIGestureRecognizer) {
if let sectionNumber = sectionNumber{
delegate?.headerViewTap(sectionNumber)
}
}
}
TableView and Custom Header delegates
extension ViewController : UITableViewDelegate, UITableViewDataSource{
//The number of sections fits the number of cells, the current list is an array of DataObject, holding a title and a content.
func numberOfSections(in tableView: UITableView) -> Int {
return self.currentList.count
}
//Each section(group of cells) contains one row
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
return cell
}
//update heights for row if the header has been taped
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let isExpanded = self.currentList[indexPath.section].isExpand
if isExpanded {
return UITableView.automaticDimension
}
return 0
}
//update the estimatedHeightForRowAt if the hader has been taped
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let isExpanded = self.currentList[indexPath.section].isExpand
if isExpanded{
return UITableView.automaticDimension
}
return 0
}
//returns a custom header
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header") as! CustomHeaderView
return headerView
}
}
extension ViewController : CustomeHeaderViewDelegate{
func headerViewTap(_ section: Int) {
selectedItem = self.currentList[section]
let output = self.currentList.map({ (item:DataItem) -> DataItem in
var result = item
if result.title == self.selectedItem?.title{
result.isExpand = !result.isExpand
}
return result
})
self.currentList = output
self.tableView.reloadSections(IndexSet(integer: section), with: UITableView.RowAnimation.automatic)
self.tableView.endUpdates()
}
}
I'm building an iOS app with a collection view inside the table view. I'm having three rows each row having a collection view inside it. I am planning to have three sections each section for each row. For example row, one should be in a separate section with a header and similarly for rows 2 and 3.
Whenever I create three sections I'm getting all the three rows in all three sections. I want to have a separate section with a header for each row.
import UIKit
class StoreVC: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var CourseTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
CourseTableView.tableFooterView = UIView()
CourseTableView.delegate = self
CourseTableView.dataSource = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0
{
return "Courses"
}
else if section == 1
{
return "Tests"
}
return "Bundles"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CourseRow
return cell
}
else if indexPath.row == 1
{
let cell = tableView.dequeueReusableCell(withIdentifier: "testcell", for: indexPath) as! TestRow
return cell
}
else if indexPath.row == 2
{
let cell = tableView.dequeueReusableCell(withIdentifier: "bundlecell", for: indexPath) as! BundleRow
return cell
}
return UITableViewCell()
}
}
Try this code on your Xcode-playground and customize as you need.
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UILabel()
headerView.text = "Header: \(section)"
return headerView
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Cell: \(indexPath)"
return cell
}
}
PlaygroundPage.current.liveView = ViewController()
I am working on an ios project which uses tableview. I could populate tableviews properly but now I want two cells in a tableview.
This is a fixed cell one which I could put datepicker into
This should be populated with data maybe from a storage or hard coded.
this means I want the first cell to be fixed because it contains the datepicker alone.
this is my code so far for implementing the (2) requirement which means I am only able to implement one cell.
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = animals[indexPath.row]
return cell
}
any help would be appriciated. thanks
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}else{
return animals.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_IDENTIFIER_FOR_DATEPICKER_CELL", for: indexPath)
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_IDENTIFIER_FOR_DEFAULT_CELL", for: indexPath
cell.textLabel?.text = animals[indexPath.row]
return cell
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
You can make 2 sections for this::
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
override func numberOfSections(in tableView: UITableView) -> Int {
return 2 // one for cell 1, and other for 2nd types
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 { return 1 } // 1 for datePicker
else { return animals.count } // requirement 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.isHidden= true
var datePicker = ...
// create date Picker and cell.contentView.addSubview(datePicker)
} else {
cell.textLabel?.text = animals[indexPath.row]
}
return cell
}
I am trying to make a FAQ view controller using table view, I need little bit fix in the UI. here is the display of my FAQ VC right now
(please ignore the red line)
as we can see, basically there are 2 row height, 80 & 160. if the row is tapped (selected) the row height will expand from 80 (yellow line) to 160 (purple line).
I want to make the row height under the last question is still 80 not 160. I have tried but I can't set the row below the last question. here is my code I use
class FAQVC: UIViewController {
#IBOutlet weak var retryButton: DesignableButton!
#IBOutlet weak var tableView: UITableView!
var FAQs = [FAQ]()
var selectedIndexs: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
getFAQData()
}
}
extension FAQVC : UITableViewDelegate, UITableViewDataSource {
// MARK: - Table View Delegate and Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.cellIsSelected(indexPath: indexPath) {
return 160
} else {
return 80
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let isSelected = !self.cellIsSelected(indexPath: indexPath)
selectedIndexs[indexPath] = isSelected
tableView.beginUpdates()
tableView.endUpdates()
}
func cellIsSelected(indexPath: IndexPath) -> Bool {
if let number = selectedIndexs[indexPath] {
return number
} else {
return false
}
}
}
Please put this code in your viewDidLoad() method of your controller.
YOUR_TABLE_VIEW.tableFooterView = UIView(frame: .zero)
this will remove extra separators.
Quickest way to do this would be to add an extra empty cell at the end with row height 80
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count + 1
}
Also, make sure you make a change to cellForRowAt method to accommodate this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < FAQs.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
return UITableViewCell()
}
EDIT :
I just read that you don't really require separators after the last cell. In that case look here https://spin.atomicobject.com/2017/01/02/remove-extra-separator-lines-uitableview/
I have a UITableViewCell which contains a UICollectionView on top and a UITableView on the bottom. The idea is that a dynamic amount of cells will be created in the inner UITableView and the parent UITableViewCell that encloses the two subviews will increase its height proportionally.
I am trying to take advantage of the estimatedRowHeight + UITableViewAutomaticDimentionfeature of the UITableViewCell that will allow the cell height to increase dynamically. However, it is not working. It completely removes the embedded UITableView from view.
I have not made any constraints that limit the height of the enclosed UITableView, so I am not sure why it is not working.
Here is the implementation that attempts to make a dynamically sized UITableViewCell:
class OverviewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Enclosed Table View Example"
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 325 // Height for inner table view with 1 cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "appHeaderCell") as! AppHeaderCell
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
return cell
}
}
My only guess is that the constraint bottom = Inner Table View.bottom + 7 is causing the issue, but the entire view falls apart when this constraint is removed.
What can I do to make the complex outer UITableViewCell dynamically adjust height based on the number of cells in the embedded UITableView?
Although it may seem like a good idea, the use of UITableViewAutomaticDimension in conjunction with estimatedRowHeight is not good to use in scenarios like this where we have general content inside table view cells. Making use of the heightForRowAt method, you can calculate the size of each individual cell before it centers the table.
Once we know how many cells will be in the inner table, you need to create an array whose elements correspond to the number inner cells that will ultimately determine the height of the outer cell, as all other content is constant.
let cellListFromData: [CGFloat] = [3, 1, 4]
This array will give us the number of sections in our outer table view:
func numberOfSections(in tableView: UITableView) -> Int {
return cellListFromData.count
}
We will convert each element in this array to a cell height in the following way:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let prototypeCell = tableView.dequeueReusableCell(withIdentifier: "appCell") as! AppCell
let baseHeight = betweenCellSpacing + prototypeCell.innerCollectionView.contentSize.height + prototypeCell.innerTableView.sectionHeaderHeight + outerTableView.sectionHeaderHeight
let dynamicHeight = prototypeCell.innerTableView.contentSize.height - prototypeCell.innerTableView.sectionHeaderHeight
return baseHeight + (dynamicHeight * cellListFromData[indexPath.section])
}
That is, inside of the heightForRowAt method, we dequeue a prototype cell that will not be used in the resulting view (as dequeueReusableCell is not called inside cellForRowAt in this case). We use this prototype cell to extract information about what is constant and what is dynamic about the cell's content. The baseHeight is the accumulated height of all the constant elements of the cell (plus the between-cell spacing) and the dynamicHeight is the height of an inner UITableViewCell. The height of each cell then becomes baseHeight + dynamicHeight * cellListFromData[indexPath.section].
Next, we add a numberOfCells variable to the class for the custom cell and set this in the cellForRowAt method in the main table view:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
cell.numberOfCells = Int(cellListFromData[indexPath.section])
cell.innerTableView.reloadData()
return cell
}
numberOfCells is set with the same cellListFromData that we used to get the height of the cell. Also, it is critical to call reloadData() on the inner table view after setting its number of cells so that we see that update in the UI.
Here is the full code:
class OverviewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var outerTableView: UITableView!
let cellSpacing: CGFloat = 25
let data: [CGFloat] = [3, 1, 4]
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let prototypeCell = tableView.dequeueReusableCell(withIdentifier: "appCell") as! AppCell
let baseHeight = cellSpacing + prototypeCell.innerCollectionView.contentSize.height + prototypeCell.innerTableView.sectionHeaderHeight + outerTableView.sectionHeaderHeight
let dynamicHeight = prototypeCell.innerTableView.contentSize.height - prototypeCell.innerTableView.sectionHeaderHeight
return baseHeight + (dynamicHeight * data[indexPath.section])
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
cell.numberOfCells = Int(data[indexPath.section])
cell.innerTableView.reloadData()
return cell
}
}
class AppCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var innerCollectionView: UICollectionView!
#IBOutlet weak var innerTableView: UITableView!
var numberOfCells: Int = 0
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "featureHeaderCell") as! BuildHeaderCell
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "innerCell", for: indexPath) as! InnerCell
return cell
}
}
Methods relating to configuring the inner collection view is not included here as it is not related to the problem.