I put UITableViewCell by xib file.
And, I want to change the height of Cell depending on the height of UITextView put in Cell.
I found an online article.
I think that it went according to this article.
But TableViewCell does not change depending on the height of TextView.
Please tell me where is wrong.
I will detail in detail what I did below.
1. I created UITableView and Cell (Xib) as shown below.
TableView.swift
import UIKit
class TableView: UIView, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
#IBOutlet weak var newImage: UIImageView!
//etc...
class func instance() -> TableView {
return UINib(nibName: "TableView", bundle: nil).instantiate(withOwner: self, options: nil)[0] as! TableView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = newTableView.dequeueReusableCell(withIdentifier: "NewCell", for: indexPath) as! NewCellTableViewCell
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
}
Cell.swift
import UIKit
class NewCellTableViewCell: UITableViewCell {
let bottomBorder = CALayer()
let vArchColor = archColor()
#IBOutlet weak var textView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
bottomBorder.frame = CGRect(x: 20, y: self.frame.size.height - 1.0, width: self.frame.width*2, height: 1.0)
bottomBorder.backgroundColor = vArchColor.black01.cgColor
self.layer.addSublayer(bottomBorder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
print("Selected")
}
}
I set several necessary values in UITableView.
// in TableView.swift
func textViewDidChange(_ textView: UITextView) {
newTableView.beginUpdates()
newTableView.endUpdates()
}
// in TableView.swift
func initSomething(){
newTableView.rowHeight = UITableViewAutomaticDimension
newTableView.estimatedRowHeight = 10000
}
I used Storyboard in Cell (XibFile) and set constraints.
Of course, I also unchecked scrollenabled.
Image of Cell.xib
You've provided an implementation for heightForRowAtIndexPath: which will override on a per row basis any constant value you have set in tableView.rowHeight = .... Remove this method from your TableViewController and you should be golden, provided the constraints of your text view aren't ambiguous. Make sure it's pinned on all four sides and has a height constraint.
Related
I have an UITableView and inside UITableViewCell, I have added view and trying to set the height of that view or height of the cell according to screen height or UITableView's height. Initially, I was able to do it but when in the viewWillAppear reloading table, not able to set the proper height for that view or height of the cell. It's the height or some other problem, not able to identify. Any help would be appreciated and Thanks for the reply. The title of the question may be confusing or do you find any unrelated code then suggestions(edits) are welcome.
Code:
import UIKit
class PageViewController: UIViewController {
#IBOutlet weak var tblPager: UITableView!
let screenHeight = UIScreen.main.bounds.size.height
let vColors: [UIColor] = [.red, .blue, .orange, .brown, .cyan, .darkGray, .green, .red, .blue, .orange, .brown, .cyan, .darkGray, .green]
var pageSizeHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
pageSizeHeight = screenHeight - (getTopSafeArea() + getBottomSafeArea() + 54)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
delay(interval: 10.0) {
print("Reload table after 10 seconds")
self.tblPager.reloadData()
}
}
}
//MARK:- TableView Delegate
extension PageViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return vColors.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PageCell", for: indexPath) as! PageCell
cell.backgroundColor = vColors[indexPath.row]
cell.viewHeight.constant = pageSizeHeight
return cell
}
}
class PageCell: UITableViewCell {
#IBOutlet weak var vwPage: UIView!
#IBOutlet weak var viewHeight: NSLayoutConstraint!
}
func getTopSafeArea() -> CGFloat {
let window = UIApplication.shared.keyWindow
let topPadding = window?.safeAreaInsets.top ?? 0
return topPadding
}
func getBottomSafeArea() -> CGFloat {
let window = UIApplication.shared.keyWindow
let bottomPadding = window?.safeAreaInsets.bottom ?? 0
return bottomPadding
}
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
closure()
}
}
Output:
Link it to tableView's height, your table view has all sizes you need.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) {
// ...
cell.viewHeight.constant = tableView.frame.size.heigh
// ...
}
or remove viewHeight constraint and use delegate method to set row height
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.frame.size.height
}
UPD:
TableView using estimatedRowHeight to calculate its scroll offsets. This does create an issue during reload if we have enabled paging and do not configure estimatedRowHeight.
Solution 1:
Implement estimatedHeightForRowAt delegate
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.frame.size.height
}
Solution 2:
Set tableView.estimatedRowHeight = 0 and use heightForRowAt delegate instead of viewHeight constraint
if all cells will have the same height, why don't you set the rowHeight property inside viewWillLayoutSubviews method?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tblPager.rowHeight = tblPager.frame.height
}
Thanks to #Desdenova for correcting me
By setting dataSource and delegate of tblPager to self in PageViewController's viewDidLoad and setting the rowHeight property in viewDidLayoutSubviews, tableView behaves as expected.
override func viewDidLoad() {
super.viewDidLoad()
tblPager.register(UINib(nibName: "PageTableViewCell", bundle: nil), forCellReuseIdentifier: "PageCell")
tblPager.dataSource = self
tblPager.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tblPager.rowHeight = tblPager.frame.size.height
}
I'm developing an iOS app in Swift and I have a UITableView with custom UITableViewCells that have spacing at the left and right of the cell by overriding layoutSubviews().
This works by subtracting 40 to self.bounds.size.width.
As I'm not really good at explaining, here you have an image:
But the problem is that when I click on a cell, the overridden layoutSubviews() function is run again, so the cell is shrunken again as it subtracts 40 again to the already original self.bounds.size.width - 40.
How can I avoid layoutSubviews() running every time the cell is clicked and make it run only once when the view is load?
I do not know why the layoutSubviews is run again, as it is loaded in my custom UITableViewCell class.
Here you have my code:
class TBRepoTableViewCell: UITableViewCell {
#IBOutlet weak var repoLabel: UILabel!
#IBOutlet weak var urlLabel: UILabel!
#IBOutlet weak var repoIcon: UIImageView!
override func layoutSubviews() {
// Set the width of the cell
self.bounds = CGRect(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width - 40, self.bounds.size.height)
super.layoutSubviews()
}
}
class SourcesViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// note that indexPath.section is used rather than indexPath.row
print("You tapped cell number \(indexPath.row).")
}
override func viewWillAppear(_ animated: Bool) {
setEditing(false, animated: true)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TBRepoTableViewCell
let repoArray = array[indexPath.row]
cell.repoLabel.text = repoArray.repoName
cell.urlLabel.text = repoArray.repoURL
cell.repoIcon.image = repoArray.icon
self.tableView.separatorStyle = .none
// add borders
cell.layer.cornerRadius = 10
cell.clipsToBounds = true
cell.layer.masksToBounds = true
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
array.remove(at: indexPath.row)
tableView.reloadData()
}
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
}
Never modify the view's frame or bounds in layoutSubviews. The only thing you should do there is update the frame or bounds of the view's subviews based on the current size of the view.
What you should do is modify your cell view class such that it has a view that shows the indent and the rounded corners. Let's call this a "background view". Add the other subviews of the cell to this background view. Then add the background view to the cell's contentView.
Your cellForRowAt should not be changing the cell's layer. That logic belongs in the custom cell class.
I have a table view that's in a nib file and it loads the data properly. It loads the required number of cells and fills in the data correctly. The cell is a xib file as well that includes multiple views. Each cell has a custom height. I managed to set the right height per cell using:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (data[indexPath.row].height + 200)
}
The problem is that on initial load the cells don't get drawn properly like so:
Initial load image
The description area doesn't move where I made it move by changing its X and Y coordinates.
This gets fixed however when I scroll the table view beyond the initially loaded cells and go back to them, like so:
After scrolling
Upon loading the cell I move the description area below the image. This works for cells but not on the initially loaded ones. It only gets fixed once I scroll the broken cells out of view and go back to them. How do I fix this? How can I make it so that the cells get drawn properly on the initial load?
Edit: To clarify: The cells get loaded properly but aren't drawn correctly. I have to scroll out of the first few cells and scroll back to them for the cells to be drawn right.
Any help is appreciated. Thank you!
I can see that the image you'r trying to load is of larger dimension than that of the image container view.
You can try giving clipsToBound = True to the imageView.
This would most probably resolve your issue.
Thanks.
you want to increase UITableViewCell height based on image inside it, right?
Here is tableview datasource and delegate.
extension MyDataSource: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mydatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let mydata = mydatas[indexPath.row]
var cell: UITableViewCell
cell = tableView.dequeueReusableCell(withIdentifier: 'myCell.cellIdentify()')!
cell.contentView.backgroundColor = UIColor.clear
cell.backgroundColor = UIColor.clear
cell.tag = indexPath.row
cell.selectionStyle = .none
let image = UIImage(named: promotion)
(cell as! myCell).configureCell(downloadImage: image!)
let aspectRatio = (image?.size.height)!/(image?.size.width)!
let imageHeight = (cell.contentView.frame.width * aspectRatio) + 16
UIView.performWithoutAnimation {
self.myTableView?.beginUpdates()
self.rowHeights[indexPath.row] = imageHeight
self.myTableView?.endUpdates()
}
return cell
}
}
here is UITableViewCell custom class.
myCell
import UIKit
class myCell: UITableViewCell {
#IBOutlet weak var imageHolder: WMView!
#IBOutlet weak var actionImage: UIImageView!
#IBOutlet weak var loaderIndicator: UIActivityIndicatorView!
override func prepareForReuse() {
super.prepareForReuse()
self.loaderIndicator.isHidden = false
self.loaderIndicator.startAnimating()
self.actionImage.image = nil
}
static func cellHeight() -> CGFloat {
return 73.0
}
static func cellIdentify() ->String {
return "myCell"
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(downloadImage: UIImage) {
self.imageHolder.layer.borderWidth = 1
self.imageHolder.layer.cornerRadius = 5
self.imageHolder.layer.borderColor = UIColor.clear.cgColor
self.imageHolder.layer.masksToBounds = true
self.imageHolder.clipsToBounds = true
//ImageHolder
self.actionImage.image = downloadImage
self.loaderIndicator.isHidden = true
}
}
I am trying to make a dynamic cell using stack view embedded in a cell so when items are added to the cell the height of cell adapt to the height of the stack view but not getting the expected results meaning I get ambitious constraints.
Here is how to set the setup is.I have a cell with an embedded stack view:
class MyCell: UITableViewCell {
override var reuseIdentifier: String? {
return "cell"
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBOutlet public weak var stack: UIStackView?
}
This is how the nib looks like:
in table view code I have the following:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCell
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
for _ in 0...10 {
let view = MyView()
cell.stack!.addArrangedSubview(view)
}
}
return cell
}
where MyView is:
class MyView: UIView {
override var intrinsicContentSize: CGSize {
setContentHuggingPriority(.defaultHigh, for: .vertical)
backgroundColor = .blue
return CGSize(width: 50, height: 50)
}
}
It seems the mistake was coming from anchoring the bottom of stack view to the bottom of the content view, instead one should anchor the bottom of the last view on the stack to the bottom of the contentView.
I need an auto scrolling Image slider in top of viewcontroller followed by list of some entities(dynamic cells with image and title). To implement that I have taken a uitableview and I'm adding scrollview to my first cell, and my code is as follows
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0{
return 200
}
else {
return 50
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.row == 0 {
let sv = UIScrollView(frame: CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height))
sv.auk.show(url: "url for image1")
sv.auk.show(url: "url for image2")
cell.addSubview(sv)
print("inside if")
}
else {
print("else")
cell.textLabel?.text = "cool"
}
return cell
}
I'm using this repository to create image slider which creates slider on a scrollview, So for first cell I have added scrollview. But as you can see in the picture the image slider reappears on multiple rows. Please tell me what is the mistake that I'm doing.In case if there is any better approaches please suggest .
Try taking a separate class for dynamic cells. Dequeue both the cells (static and dynamic cells) in the cellForRow method as follows:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.row == 0 ? 200 : 50
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
scrollViewWidth = cell.frame.width
scrollViewHeight = cell.frame.height
let scrollView = prepareScrollView(width: scrollViewWidth, height: scrollViewHeight)
cell.addSubview(scrollView )
print("First row")
return cell
}
else {
let myCustomCell: MyCustomTableViewCellClass = tableView.dequeueReusableCell(withIdentifier: "MyCustomTableViewCellIdentieier", for: indexPath) as! MyCustomTableViewCellClass
myCustomCell.textLabel?.text = "Cool"
print("Other dynamic rows")
return myCustomCell
}
}
func prepareScrollView(_ width: Float, height: Float) -> UIScrollView {
let scrollViewFrame = CGRect(x: 0, y: 0, width: width, height: height)
let scrollView = UIScrollView(frame: scrollViewFrame)
scrollView.auk.show(url: "url for image1")
scrollView.auk.show(url: "url for image2")
return scrollView
}
Take a separate class as MyCustomTableViewCellClass of type UITableViewCell and subclass your dynamic cell with this class. Don't forget to give cell identifier as "MyCustomTableViewCellIdentieier"
After this, your static cell will dequeue only once and no chances od repeating UI elements
You can try this code in your table view cell class -
override func prepareForReuse() {
//set your lable and image view to nil
}
SideMenuTVCell is a my custom UITableViewCell class -
Hope like this you have your own class within this class you add prepareForReuse() method -
class SideMenuTVCell: UITableViewCell {
#IBOutlet weak var iconIView: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
}
override func prepareForReuse() {
//set your lable and image view to nil
self.iconIView.image = nil
self.lblTitle.text = nil
}
}