Dynamic UITableView with images - swift

There are similar questions, but non of the answers worked for me. In a dynamic table I want to display images that have different heigh. Each cell has a UIImageView with contentMode = .scaleAspectFit so the image nicely takes the width of the table and takes the height as much as needed.
Cell has 4 constraints:
Table view controller:
class TableTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ImageTableViewCell.self), for: indexPath) as! ImageTableViewCell
let image = indexPath.row == 0 ? UIImage(named: "1.jpg")! : UIImage(named: "2.jpg")!
cell.customImageView.image = image
return cell
}
}
Result:
As you can see that the height of the cell is incorrect (red background of the image view on top and bottom of the image view). I believe this happens because intrinsicContentSize of the image view is equal to the image size and thats why the height of the cell is calculated incorrectly (content mode is not taken into account). I tried calculating height of the image and adding height constraint for the image view:
cell.heightConstraint.constant = cell.frame.width * image.size.height / image.size.width
but it breaks cell's content view constraints.
The project can be downloaded here>>

In your ImageTableViewCell.swift
import UIKit
class ImageTableViewCell: UITableViewCell {
#IBOutlet weak var customImageView: UIImageView!
internal var aspectConstraint : NSLayoutConstraint? {
didSet {
if oldValue != nil {
customImageView.removeConstraint(oldValue!)
}
if aspectConstraint != nil {
customImageView.addConstraint(aspectConstraint!)
}
}
}
override func prepareForReuse() {
super.prepareForReuse()
aspectConstraint = nil
}
func setPostedImage(image : UIImage) {
let aspect = image.size.width / image.size.height
aspectConstraint = NSLayoutConstraint(item: customImageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: customImageView, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
customImageView.image = image
}
}
And into your TableTableViewController.swift your cellForRowAt method will be:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) as! ImageTableViewCell
let image = imageArr[indexPath.row]
cell.setPostedImage(image: image!)
return cell
}
And declare your imageArr this way:
let imageArr = [UIImage(named: "1.jpg"), UIImage(named: "2.jpg")]
And your compete code will be:
import UIKit
class TableTableViewController: UITableViewController {
let imageArr = [UIImage(named: "1.jpg"), UIImage(named: "2.jpg")]
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) as! ImageTableViewCell
let image = imageArr[indexPath.row]
cell.setPostedImage(image: image!)
return cell
}
}
And THIS will be your result.
EDIT:
To fix constraint issue set aspectConstraint priority to 999 and aspectConstraint will be:
internal var aspectConstraint : NSLayoutConstraint? {
didSet {
if oldValue != nil {
customImageView.removeConstraint(oldValue!)
}
if aspectConstraint != nil {
aspectConstraint?.priority = 999 //add this
customImageView.addConstraint(aspectConstraint!)
}
}
}

Related

How can I prevent an image from appearing in table view section header cell when it is not set

I have a table view with section cells and one cell in each section to display an image when the section cell is clicked on. Upon first click there is no image in the section header cell (which is the expected behavior). But when I click on it a secong time the image appears in the section header cell. I am not sure why this is happening and how to prevent it.
Here is the code where I deal with any images and set the images for specific cells.[enter image description here](https://i.stack.imgur.com/uGsBA.png)
import UIKit
class expandedSection {
let title: String
var isOpened: Bool = false
var image: UIImage!
init(title: String,
isOpened: Bool = false, image: UIImage) {
self.title = title
self.isOpened = isOpened
self.image = image
}
}
class AnalyticsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
/*
Variables
*/
#IBOutlet var buttons: [UIButton]!
#IBOutlet weak var tableView: UITableView!
var sectionImages = [UIImage(named: "graph1"), UIImage(named: "graph1")]
private var sections = [expandedSection]()
/*
Constructor
*/
override func viewDidLoad() {
// set up models
sections.insert(expandedSection(title: "Title", image: sectionImages[0]!), at: 0)
view.backgroundColor = UIColor(red: 127/255, green: 204/255, blue: 204/255, alpha: 1)
super.viewDidLoad()
self.tableView.register(UINib.init(nibName: "ExercisesTableViewCell", bundle: .main), forCellReuseIdentifier: "ExercisesTableViewCell")
tableView.dataSource = self
tableView.delegate = self
self.navigationItem.title = "Progression"
}
/*
TableView Initializers - Legs
*/
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
// creates number of rows in sections based off the options array length + 1 (to account for the section header)
if section.isOpened {
return 2
}
else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ExercisesTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ExercisesTableViewCell", for: indexPath) as! ExercisesTableViewCell
if indexPath.row == 0 {
cell.textLabel?.text = sections[indexPath.section].title
cell.textLabel?.textColor = UIColor.init(red: 73/255, green: 72/255, blue: 178/255, alpha: 1)
}
else {
cell.selectionStyle = .none
cell.textLabel?.text = ""
cell.imageView?.image = sections[indexPath.section].image
cell.imageView?.bottomAnchor.constraint(equalTo: cell.bottomAnchor, constant: 3).isActive = true
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row != 0 {
return 400.0
}
else {
return 50
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.row == 0 {
sections[indexPath.section].isOpened = !sections[indexPath.section].isOpened
tableView.reloadSections([indexPath.section], with: .automatic)
}
}
}
I have tried specifically setting the image to hidden on those cells but that still results in the text in the section headers moveing to the right to accomodate for space.
It looks like this is a layout issue as the UITableView code you've posted, while rather messy, looks to be logically sound.
I suggest that you use the built-in iOS table view section headers as it is designed to handle what you're looking to do. As a starting point, have a look at the viewForHeaderInSection docs.

How to make changes to UITableViewCell from navigationBar?

How can I make layout changes to a UITableViewCell from the navigation bar of a UITableViewController? It feels like I've tried everything, but to no avail.
This is the controller:
class TableViewController: UITableViewController {
let tableViewCellID = "tableViewCellID"
//MARK: Loading view
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableViewCell.self, forCellReuseIdentifier: tableViewCellID)
navigationBar()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: tableViewCellID) as? TableViewCell else { return UITableViewCell() }
tableViewCell.firstLayout()
return tableViewCell
}
func navigationBar() {
let emptyButton = UIBarButtonItem(image: UIImage(named: "icon"), style: .done, target: self, action: #selector(buttonTapped))
navigationItem.rightBarButtonItems = [emptyButton]
navigationItem.largeTitleDisplayMode = .never
}
#objc func buttonTapped() {
let tableViewCell = TableViewCell()
tableViewCell.secondLayout()
}
}
This is the cell:
class TableViewCell: UITableViewCell {
let firstColorView: UIView = {
let coverView = UIView()
coverView.translatesAutoresizingMaskIntoConstraints = false
coverView.backgroundColor = .red
return coverView
}()
let secondColorView: UIView = {
let cameraViewClosure = UIView()
cameraViewClosure.translatesAutoresizingMaskIntoConstraints = false
cameraViewClosure.backgroundColor = .yellow
return cameraViewClosure
}()
func firstLayout() {
contentView.addSubview(firstColorView)
NSLayoutConstraint.activate([
firstColorView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1),
firstColorView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1),
])
}
func secondLayout() {
print("This prints, but no changes are made to the cell")
contentView.addSubview(secondColorView)
NSLayoutConstraint.activate([
secondColorView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5),
secondColorView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1),
secondColorView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
])
}
}
I have of course tried all these:
layoutIfNeeded()
setNeedsLayout()
setNeedsDisplay()
layoutSubviews()
setNeedsUpdateConstraints()
updateConstraints()
updateConstraintsIfNeeded()
... and I have tried them in all kinds combinations, but I still don't get the results I want.
Let me know if you need anymore code or information, please and thank you in advance for any help!
Try the following modifications.
#objc func buttonTapped() {
let cell = tableView.visibleCells.first as? TableViewCell
cell?.secondLayout()
}

add TestField in new row When Button Touhed swift

there is a table view and i create a button in Second cell . i handled Button Touch event with protocol .i want to add a empty text field in new row at current section Exactly below of phone number text field when add button touched but i cant handle it .
table view cell :
protocol InsertTableCellDelegate {
func insertTableCellDelegate_addrowbtn(sender:InsertTableCell1)
}
class InsertTableCell1: UITableViewCell {
var delegate : InsertTableCellDelegate?
#IBOutlet weak var tbCreateRow: UITableView!
#IBOutlet weak var txtPhoneNumber: JVFloatLabeledTextField!
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func btn_Add_DidTouch(_ sender: Any) {
if let delegate = self.delegate
{
delegate.insertTableCellDelegate_addrowbtn(sender: self)
}
}
}
table view Delegates :
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell0 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell0") as! InsertTableCell0
return cell0
}else if indexPath.section == 1 {
//this section should handle add textfield in new row when text button touhed
let cell1 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell1") as! InsertTableCell1
cell1.delegate = self
return cell1
}else {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell2") as! InsertTableCell2
cell2.txtEmail.text = strEditEmail
return cell2
}
}
//protocol of button :
func insertTableCellDelegate_addrowbtn(sender: InsertTableCell1) {
print("touched")
}
numberOfRowsInSection have fixed number of cell. First you need to create a
dynamic dataSource.
sample code :
var dataSource : [Int] = Array.init(repeating: 1, count: 3)
func numberOfSections(in tableView: UITableView) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource[section]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell0 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell0") as! InsertTableCell0
return cell0
}else if indexPath.section == 1 {
//this section should handle add textfield in new row when text button touhed
let cell1 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell1") as! InsertTableCell1
cell1.delegate = self
return cell1
}else {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell2") as! InsertTableCell2
cell2.txtEmail.text = strEditEmail
return cell2
}
}
//protocol of button :
func insertTableCellDelegate_addrowbtn(sender: InsertTableCell1) {
dataSource[1] = dataSource[1] + 1
tableView.reloadData() // or you can use beginUpdates() and endUpdates()
}
Sample of code for add new Row when click on add button in Section
//Create outlet for table view
#IBOutlet weak var currentInsetTableView: UITableView!
//Declare these variable
let reuseInsetCellIdentifier = "insertCell";
let titleSection = ["Add Phone", "Add Email"]
var arrayForCellInSection = Array<Any>()
//Add this code in viewDidLoad()
currentInsetTableView.delegate = self
currentInsetTableView.dataSource = self
currentInsetTableView.estimatedRowHeight = 200
currentInsetTableView.rowHeight = UITableView.automaticDimension
currentInsetTableView.register(UINib(nibName: "CurrentInsertTableViewCell", bundle: nil), forCellReuseIdentifier: reuseInsetCellIdentifier)
arrayForCellInSection = Array.init(repeating: 1, count: titleSection.count)
//Implematation of UITablewView Delegate
func numberOfSections(in tableView: UITableView) -> Int {
return titleSection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayForCellInSection[section] as! Int
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseInsetCellIdentifier, for: indexPath)
as! CurrentInsertTableViewCell
cell.backgroundColor = UIColor.black
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 50))
headerView.backgroundColor = UIColor.black
let buttonX = 10
let buttonY = 10
let buttonWidth = 30
let buttonHeight = 30
let button = UIButton(type: .system)
let imgAdd = UIImage(named: "Add")
button.setImage(imgAdd, for: .normal)
button.addTarget(self, action: #selector(buttonClickedForInsertRowInSection(sender:)), for: .touchUpInside)
button.frame = CGRect(x: buttonX, y: buttonY, width: buttonWidth, height: buttonHeight)
button.tag = section
headerView.addSubview(button)
let label = UILabel()
label.frame = CGRect.init(x: button.frame.size.width + CGFloat((2 * buttonX)), y: CGFloat(buttonY), width: headerView.frame.width - button.frame.size.width + CGFloat((3 * buttonX)), height: headerView.frame.height - CGFloat((2 * buttonY)))
label.text = titleSection[section]
label.textColor = UIColor.white
headerView.addSubview(label)
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
//CurrentInsertTableViewCell Desing
//Final Result: When we press two times on first section add button & one times on second section add button

Ratio of TableView contentoffset with respect to cell

I have a tableview on each cell their scrollview. I have to set cell scrollview offset on table scroll.How we make ration of tableconentent offset with cell Please help I am newer in iOS. I m using JbParralax
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let indexpath = table.indexPathsForVisibleRows
let a : Int = (indexpath?.count)! - 1
for i in 0...a {
let b = indexpath?[i]
let cell = table.cellForRow(at: b!) as? Custum
cell?.myScrollView.contentOffset.y = table.contentOffset.y
}
}
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Custum"
var cell: Custum! = tableView.dequeueReusableCell(withIdentifier: identifier) as? Custum
if cell == nil {
var nib:Array = Bundle.main.loadNibNamed("Custum", owner: self, options: nil)!
cell = nib[0] as? Custum
}
//cell.myScroll
cell.myImage.image = UIImage(named:banner[indexPath.row])
return cell
}
}

Swift tableview cell auto height with auto height label

I have some problems with swift.
Need to make tableview and in every cell to have image with text inside.
This is what i made so far:
First problem:
Label should be auto height, now it breaks string..
Second problem:
Image needs to be auto height too, and to depends on label height.
Third problem:
Row needs to be autoheight depending on its inside.
TableViewController code:
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 44.0;
tableView.tableFooterView = UIView(frame: CGRectZero)
tableView.registerNib(UINib(nibName: "QuoteTableViewCell", bundle: nil), forCellReuseIdentifier: "QuoteCell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("QuoteCell", forIndexPath: indexPath) as! QuoteTableViewCell
cell.item = results[indexPath.row]
return cell
}
Cell code:
class QuoteTableViewCell: UITableViewCell {
var item: Quote! {
didSet {
setupCell()
}
}
#IBOutlet weak var imageField: UIView!
#IBOutlet weak var textField: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setupCell() {
self.imageField.backgroundColor = UIColor(patternImage: UIImage(named: "Bg")!)
textField.text = item.text
}
}
To have the label adapt its height to its contents, set its Lines number to 0 in Storyboard.
If you want to have an image next to it, why not to put both controls into one horizontal StackView?
And to have the tableView row height adapt to the cell contents (i.e. label height), just set it's rowHeight to automatic:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 90.0;
Use this metod to have that functionality
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let lineBrakingMode = NSLineBreakMode.ByCharWrapping
let paragragh:NSMutableParagraphStyle = NSMutableParagraphStyle()
paragragh.lineBreakMode = lineBrakingMode
let attributes:NSDictionary = [NSFontAttributeName: self.textField.font!, NSParagraphStyleAttributeName:paragragh]
let TextSize = self.textField.text!.sizeWithAttributes(attributes)
let TextHeight = TextSize.height + 5;
UIView.animateKeyframesWithDuration(0.2, delay: 2.0, options: UIViewKeyframeAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.imageField.frame.height = TextHeight
}, completion: nil)
return TextHeight
}
}