How to make changes to UITableViewCell from navigationBar? - swift

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()
}

Related

What is this extra row in the UITabBar (Swift)

I programmatically add a TableView in a ViewController, then appeared an extra row. I would like to be able to manage it, but first I would have to know what it is. This line seems to come from the TabBar rather than the TableView. Do you know which object it is?
The code:
import UIKit
final class SearchViewController2: UIViewController, UITableViewDataSource, UITableViewDelegate {
private let ingredients = [
"flour",
"sugar",
"eggs",
"butter",
"baking powder",
"vanilla extract",
"milk",
"salt",
"cocoa powder",
"yeast",
"olive oil",
"cheese",
"breadcrumbs",
"onions",
"garlic",
"tomatoes",
"basil",
"oregano",
"thyme",
"parsley",
"pepper"
]
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let ingredient = ingredients[indexPath.row]
var cellConfig = cell.defaultContentConfiguration()
cellConfig.text = ingredient
cell.contentConfiguration = cellConfig
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
ingredients.count
}
override func viewDidLoad() {
super.viewDidLoad()
let header = StretchyTableHeaderView(
frame: CGRect(
x: 0,
y: 0,
width: view.frame.size.width,
height: view.frame.size.width / 2
)
)
view.addSubview(tableView)
header.imageView.image = UIImage(named: "header")
tableView.dataSource = self
tableView.delegate = self
tableView.frame = view.bounds
tableView.tableHeaderView = header
}
}
extension SearchViewController2: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let header = tableView.tableHeaderView as? StretchyTableHeaderView else { return }
header.scrollViewDidScroll(scrollView: tableView)
}
}
I tried to add/remove a footer in the TableView... No changes.

How I can pin the tableHeaderView?

Tell me, please, how I can pin the tableHeaderView on the ViewController's screen through code? I use this code, but the tableViewHeader disappears on scrolling:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
createTable()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .grouped)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableHeaderView = "Test Header"
view.addSubview(tableViewTest)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
}
This is my second class:
import UIKit
class TestTableViewCell: UITableViewCell {
let testLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
testLabel.frame = CGRect(x: 60, y: 5, width: UIScreen.main.bounds.width - 80, height: 50)
testLabel.numberOfLines = 0
testLabel.sizeToFit()
addSubview(testLabel)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try to use the implicit construct of UITableView
lazy var tableViewTest = UITableView(frame: .zero, style: .plain)
But also you need to change your table header to section header which you can define in UITableViewDelegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = YourSectionHeaderView.reuseIdentifier
let headerView =
tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier)
return headerView
}
By the way, I also recommend you to use constraint instead of autoresizing mask
Thank you.
This work for me:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
var segmentedControl = UISegmentedControl(items: ["Test1", "Test2", "Test3"])
override func viewDidLoad() {
super.viewDidLoad()
createTable()
configureSegmentedControl()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .plain)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableFooterView = UIView()
view.addSubview(tableViewTest)
}
private func configureSegmentedControl() {
segmentedControl.selectedSegmentIndex = 0
segmentedControl.frame = CGRect(x: 10, y: 150, width: UIScreen.main.bounds.width - 20.0, height: 40)
segmentedControl.layer.cornerRadius = 5.0
segmentedControl.backgroundColor = .blue
segmentedControl.selectedSegmentTintColor = .red
segmentedControl.tintColor = .white
segmentedControl.addTarget(self, action: #selector(changeSegment), for: .valueChanged)
}
#objc func changeSegment(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
print("Test1")
case 1:
print("Test2")
default:
print("Test3")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return segmentedControl
}
}

Adding two UITableViews in one UIVIewController programmatically and want to change the View of Controller with UISegmentedControl

I want to change the View of my ViewController based on 2 tableVIews with segmentedControl. I have reached it with programmatic approach. I created two TableViews and One SegmentedControl. But When I change the segmentedControl it stays on the same Index but changing the View. I have to tap twice on each Segment to reach it. Why it is happening?
Here is my code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureFirstTableView()
}
var firstTableView = UITableView()
var secondTableView = UITableView()
func configureFirstTableView() {
firstTableView.frame = self.view.frame
firstTableView.delegate = self
firstTableView.dataSource = self
firstTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FirstCell")
view.addSubview(firstTableView)
}
func configureSecondTableView() {
secondTableView.frame = self.view.frame
secondTableView.delegate = self
secondTableView.dataSource = self
secondTableView.register(UITableViewCell.self, forCellReuseIdentifier: "SecondCell")
view.addSubview(secondTableView)
}
func configureSegmentedControl() -> UISegmentedControl {
let segmentedControl = UISegmentedControl(frame: CGRect(x: 10, y: 5, width: view.frame.width - 10, height: 30))
segmentedControl.insertSegment(withTitle: "First", at: 0, animated: false)
segmentedControl.insertSegment(withTitle: "Second", at: 1, animated: false)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(changeSegmentedControlValue(_:)), for: .valueChanged)
return segmentedControl
}
#objc func changeSegmentedControlValue(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
print("1")
configureFirstTableView()
case 1:
print("2")
configureSecondTableView()
default:
break
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count: Int?
if tableView == firstTableView {
count = 3
return count!
} else if tableView == secondTableView {
count = 4
return count!
}
return count!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == firstTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath)
cell.textLabel?.text = "First"
return cell
} else if tableView == secondTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath)
cell.textLabel?.text = "Second"
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = UIView()
v.backgroundColor = .white
v.addSubview(configureSegmentedControl())
return v
}
}

Swift: Delegate of UITextField in UITableViewCell does not work

I'm trying to set the delegate of my titleTF to my UITableViewCell or my UITableView. This is how I set up the textField inside my UITableViewCell class.
class AddAppointmentTableViewCell: UITableViewCell, UITextFieldDelegate, UITextViewDelegate {
var view = UIView()
var titleTF = UITextField()
func addTitle() -> UIView {
view = UIView(frame: CGRect(x: 0, y: 0, width: frame.width, height:
NSAttributedString(string: "Enter Title", attributes: [.font: UIFont.boldSystemFont(ofSize: 24),
.foregroundColor: UIColor.lightGray]).size().height+30))
titleTF.translatesAutoresizingMaskIntoConstraints = false
titleTF.textAlignment = .left
titleTF.delegate = self
titleTF.attributedText = NSAttributedString(string: "Enter Title", attributes: [.font: UIFont.boldSystemFont(ofSize: 24),
.foregroundColor: UIColor.lightGray])
titleTF.inputAccessoryView = toolBar
view.addSubview(titleTF)
titleTF.topAnchor.constraint(equalTo: view.topAnchor, constant: 15).isActive = true
titleTF.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15).isActive = true
titleTF.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
titleTF.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15)
return view
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
}
override func draw(_ rect: CGRect) {
addSubview(view)
}
}
This is inside my UITableView class:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return rows[indexPath.row].frame.height
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AddAppointmentTableViewCell
cell.view = rows[indexPath.row]
return cell
}
And rows is equals [cell.addTitle(), cell.addNote()]
I don't understand why the textFieldDidBeginEditing method inside my UITableViewCell isn't called.
You have to call the delegate inside the awakeFromNib (had the same problem):
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
You need to add this line
{
textField.delegate = self
}

Dynamic UITableView with images

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!)
}
}
}