I'm trying to create a basic table that would have 4 columns. I'm approaching this by creating a UITableView with a custom cell which contains a horizontal stack view containing the required text views. Here is my UITableView code:
import UIKit
class HistoryTableViewController: UITableViewController {
let cellID = "HistoryColumns"
struct CellData {
let firstColumn: String?
let secondColumn: String?
let thirdColumn: String?
let fourthColumn: String?
}
var data = [CellData]()
override func viewDidLoad() {
super.viewDidLoad()
data = [CellData.init(firstColumn: "Date", secondColumn: "Flight number", thirdColumn: "Type", fourthColumn: "Pre-refuel FOB (kg)")]
tableView.register(FuelHistoryColumnCells.self, forCellReuseIdentifier: cellID)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! FuelHistoryColumnCells
cell.firstColumn = data[indexPath.row].firstColumn
cell.secondColumn = data[indexPath.row].secondColumn
cell.thirdColumn = data[indexPath.row].thirdColumn
cell.fourthColumn = data[indexPath.row].fourthColumn
return cell
}
}
And this is my custom cell code:
import UIKit
class FuelHistoryColumnCells: UITableViewCell {
var firstColumn: String?
var secondColumn: String?
var thirdColumn: String?
var fourthColumn: String?
lazy var firstColumnView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .green
return textView
}()
lazy var secondColumnView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .green
return textView
}()
lazy var thirdColumnView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .green
return textView
}()
lazy var fourthColumnView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .green
return textView
}()
lazy var fifthColumnView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .green
return textView
}()
lazy var stackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [firstColumnView, secondColumnView, thirdColumnView, fourthColumnView])
stack.axis = .horizontal
stack.distribution = .fillEqually
return stack
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(stackView)
}
override func layoutSubviews() {
super.layoutSubviews()
if let firstColumn = firstColumn {
firstColumnView.text = firstColumn
}
if let secondColumn = secondColumn {
secondColumnView.text = secondColumn
}
if let thirdColumn = thirdColumn {
thirdColumnView.text = thirdColumn
}
if let fourthColumn = fourthColumn {
fourthColumnView.text = fourthColumn
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm sure I'm missing something obvious here as nothing is showing up on the table view...
Related
I am new to swift . I am trying to create table view and cell programmatically. I want to display the three label properties one below to another . I added the content view with respective label properties . I have set the row height but when I run the app is overlapping.
Here is the my Table view code with view controller .
class RoomViewController: UIViewController {
var coordinator: RoomBaseCoordinator?
init(coordinator: RoomBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let roomviewModel = RoomViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(RoomCellTableViewCell.self, forCellReuseIdentifier: RoomCellTableViewCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
}
private func setUpUI() {
view.backgroundColor = .white
title = "Room List "
view.addSubview(tableView)
tableView.rowHeight = 100
// create constraints
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
roomviewModel
.$rooms
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
roomviewModel.getRoom()
}
}
extension RoomViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return roomviewModel.rooms.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: RoomCellTableViewCell.identifier, for: indexPath) as? RoomCellTableViewCell
else { return UITableViewCell() }
let row = indexPath.row
let room = roomviewModel.rooms[row]
cell.configureCell(createdAt: room.createdAt, IsOccupied: room.isOccupied, maxOccupancy: String(room.maxOccupancy), id: room.id)
return cell
}
}
extension RoomViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
roomviewModel.getRoom()
}
}
Here is the cell .
class RoomCellTableViewCell: UITableViewCell {
static let identifier = "RoomCell"
private lazy var createdAtLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 15)
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var IsOccupiedLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 15)
label.numberOfLines = 0
//label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.textAlignment = .left
return label
}()
private lazy var maxOccupancyLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 15)
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var idLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 15)
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// self.contentView.addSubview(containerView)
contentView.addSubview(createdAtLabel)
contentView.addSubview(IsOccupiedLabel)
contentView.addSubview(maxOccupancyLabel)
createdAtLabel.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
createdAtLabel.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
createdAtLabel.widthAnchor.constraint(equalToConstant:50).isActive = true
createdAtLabel.heightAnchor.constraint(equalToConstant:50).isActive = true
contentView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo:self.createdAtLabel.trailingAnchor, constant:10).isActive = true
contentView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
contentView.heightAnchor.constraint(equalToConstant:50).isActive = true
IsOccupiedLabel.topAnchor.constraint(equalTo:self.contentView.topAnchor).isActive = true
IsOccupiedLabel.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor).isActive = true
IsOccupiedLabel.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor).isActive = true
maxOccupancyLabel.topAnchor.constraint(equalTo:self.IsOccupiedLabel.bottomAnchor).isActive = true
maxOccupancyLabel.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor).isActive = true
maxOccupancyLabel.topAnchor.constraint(equalTo:self.IsOccupiedLabel.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(createdAt: String, IsOccupied: Bool, maxOccupancy: String, id : String ) {
createdAtLabel.text = "CreatedAt :\(createdAt)"
IsOccupiedLabel.text = "IsOccupied : \(IsOccupied)"
maxOccupancyLabel.text = "MaxOccupancy : \(maxOccupancy)"
idLabel.text = "Id : \(id)"
}
}
Here is the result .
In setUpUI() method of your RoomViewController class, set tableView's rowHeight to UITableView.automaticDimension and estimatedRowHeight to 100.
import UIKit
class RoomViewController: UIViewController {
private func setUpUI() {
view.backgroundColor = .white
title = "Room List "
view.addSubview(tableView)
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
// create constraints
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
And add UIStackView in the RoomCellTableViewCell class as bellow:
import UIKit
class RoomCellTableViewCell: UITableViewCell {
private var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leftAnchor.constraint(equalTo: leftAnchor),
stackView.rightAnchor.constraint(equalTo: rightAnchor)
])
stackView.addArrangedSubview(createdAtLabel)
stackView.addArrangedSubview(isOccupiedLabel)
stackView.addArrangedSubview(maxOccupancyLabel)
// stackView.addArrangedSubview(idLabel)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I created table view cell programmatically and set the constrains as well.It is able to display the image , firstname , lastname property into cell but when I added new label with values , it not displaying the values .
Here is the cell code .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let jobTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
containerView.addSubview(jobTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String,jobtitle: String ) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
jobTitleLabel.text = "Occupation : \(jobtitle)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
private var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(activityIndicator)
view.addSubview(tableView)
// self.tableView.rowHeight = 100.00
tableView.rowHeight = UITableView.automaticDimension
tableView.rowHeight = 100
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName,jobtitle: people.jobtitle)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the screenshot . As it not showing the job title property into cell .
I think your problem is the height of your containerView
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
I think 40 is to low to show the jobTitleLabel
I am new to swift .I want to display the records with image view in table view cell . I have defined the property with leadingAnchor , trailingAnchor, widthAnchor, heightAnchor with content view . But when I run the app it overlapping the view .
Here is the code in cell .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the result .
This is rather tricky because it seems your constraints are fine, assuming that your tableview height is 100, but the screenshot tableview cells seem a little shorter than 100. Let's assume the cell height is 100 correct.
I suggest you try configuring the imageView (and other views) in override func layoutSubViews(), which is a function that renders whenever the contentView's bound change. It should also be noted that better practice is where the imageSize is relative to the cell/contentView's frame instead of hardcoded values.
So it should look like
import UIKit
class PeopleCell: UITableViewCell {
let profileImageView:UIImageView = {
let img = UIImageView()
return img
}()
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.translatesAutoresizingMaskIntoConstraints = false profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
//You may want to try with other type of contentMode such as aspectFit, etc
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = self.frame.width / 2
profileImageView.clipsToBounds = true
}
//If above doesn't work, you may want to look into the imageConfiguration function you made and ensure that contentMode is applied properly.
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
self?.profileImageView.contentMode = .scaleAspectFill
}
}
If all of the above code doesn't work, try to find the profileImageView size values by using breakpoints or ViewHierarchy within Xcode. To check the height of image or cell itself, they should be sufficient for you to find clues to resolve the issue.
All the best.
I'm trying to make a custom textfield/sendbutton view that overrides inputAccessoryView, but I'm having 2 problems I can't seem to solve:
When the custom view becomes the first responder, I get this warning twice:
CustomKeyboardProject[5958:3107074] API error:
<_UIKBCompatInputView: 0x119e2bd70; frame = (0 0; 0 0);
layer = <CALayer: 0x283df9e00>> returned 0 width,
assuming UIViewNoIntrinsicMetric
Then when I try to resign the custom view as the first responder, Xcode throws this warning:
CustomKeyboardProject[5958:3107074] -[UIWindow
endDisablingInterfaceAutorotationAnimated:] called on <UITextEffectsWindow:
0x11a055400; frame = (0 0; 375 667); opaque = NO; autoresize = W+H; layer =
<UIWindowLayer: 0x283df78a0>> without matching
-beginDisablingInterfaceAutorotation. Ignoring.
Has anyone been able to silence these warnings?
Heres my code:
protocol CustomTextFieldDelegate: class {
func sendMessage()
}
class CustomTextField: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
}
class CustomKeyboardView: UIView {
let textField: CustomTextField = {
let textField = CustomTextField()
textField.backgroundColor = .green
textField.textColor = .white
return textField
}()
let attachButton: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setTitle("Attach", for: .normal)
return button
}()
let sendButton: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitle("Send", for: .normal)
button.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
return button
}()
#objc func sendMessage() {
delegate?.sendMessage()
}
weak var delegate: CustomTextFieldDelegate?
init() {
super.init(frame: .zero)
isUserInteractionEnabled = true
setViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setViews() {
let subviews = [attachButton, sendButton, textField]
subviews.forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
autoresizingMask = .flexibleHeight
subviews.forEach {
NSLayoutConstraint.activate([
$0.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
layoutSubviews()
[attachButton, sendButton].forEach {
NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
$0.heightAnchor.constraint(equalTo: textField.heightAnchor)
])
}
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: topAnchor),
attachButton.leftAnchor.constraint(equalTo: leftAnchor),
textField.leadingAnchor.constraint(equalTo: attachButton.trailingAnchor),
sendButton.leadingAnchor.constraint(equalTo: textField.trailingAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomTableView: UITableView {
let keyboardView = CustomKeyboardView()
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
keyboardDismissMode = .interactive
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return keyboardView
}
}
private let reuseId = "MessageCellId"
class ExampleViewController: UITableViewController {
private let customTableView = CustomTableView()
var messages = [String]()
override func loadView() {
tableView = customTableView
view = tableView
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.becomeFirstResponder()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseId)
customTableView.keyboardView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.customTableView.keyboardView.textField.becomeFirstResponder()
}
}
// tableView delegate methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId)
cell?.textLabel?.text = messages[indexPath.row]
return cell!
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
customTableView.keyboardView.textField.resignFirstResponder()
}
}
extension ExampleViewController: CustomTextFieldDelegate {
func sendMessage() {
// double check something is in textField
let textField = customTableView.keyboardView.textField
guard let body = textField.text, body != "" else { return }
messages.append(body)
tableView.reloadData()
view.endEditing(true)
customTableView.keyboardView.textField.text = ""
}
}
So, I have checked that tableview.reloadData() is being called on the main thread and that numberOfRowsInSection has values. When I look at the view hierarchy the tableView is clearly there and shows the background colour when set. What I have noticed that is odd is that the view hierarchy says its an Empty list(understandable) but it contains two UIImageView's one in the top right and one in the bottom left. Don't understand this at all. My tableView is nested in an iCarousel which I'm sure is where the issue lies. My code is entirely programmatic, so here is what i think may be of value:
iCarouselDatasource:(carousels dont have cells but views instead)
class BillCarouselDataSource: NSObject, iCarouselDataSource {
var bills = [Bill]()
func numberOfItems(in carousel: iCarousel) -> Int {
return bills.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let bill = bills[index]
let billCarouselView = BillCarouselView(bill: bill)
billCarouselView.frame = carousel.frame
billCarouselView.setupView()
return billCarouselView
}
}
iCarousel view(cell)
class BillCarouselView: UIView {
private var bill: Bill!
private let nameLabel = CarouselLabel()
private let locationLabel = CarouselLabel()
private let dateLabel = CarouselLabel()
private let splitButton = CarouselButton()
private let tableView = UITableView()
required init(bill: Bill) {
super.init(frame: .zero)
self.bill = bill
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupView() {
setupHierarchy()
setupViews()
setupLayout()
}
private func setupHierarchy() {
addSubview(nameLabel)
addSubview(locationLabel)
addSubview(dateLabel)
addSubview(tableView)
addSubview(splitButton)
}
private func setupViews() {
accessibilityIdentifier = AccesID.carouselView
isUserInteractionEnabled = true
layer.cornerRadius = Layout.carouselViewCornerRadius
backgroundColor = Color.carouselView
nameLabel.text = bill.name
nameLabel.textAlignment = .center
nameLabel.accessibilityIdentifier = AccesID.carouselNameLabel
locationLabel.text = bill.location
locationLabel.textAlignment = .left
locationLabel.accessibilityIdentifier = AccesID.carouselLocationLabel
dateLabel.text = bill.creationDate
dateLabel.font = Font.carouselDateText
dateLabel.textAlignment = .right
dateLabel.accessibilityIdentifier = AccesID.carouselDateLabel
let dataSource = CarouselTableViewDataSource()
let delegate = CarouselTableViewDelegate()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.dataSource = dataSource
tableView.delegate = delegate
tableView.register(CarouselTableViewCell.classForCoder(),
forCellReuseIdentifier: "carouselTableViewCell")
dataSource.items = bill.items
tableView.reloadData()
let buttonTitle = "Split £\(bill.totalPrice())"
splitButton.setTitle(buttonTitle, for: .normal)
}
private func setupLayout() {
nameLabel.pinToSuperview(edges: [.top, .left, .right])
locationLabel.pinToSuperview(edges: [.left],
constant: Layout.spacer,
priority: .required)
locationLabel.pinTop(to: nameLabel, anchor: .bottom)
locationLabel.pinRight(to: dateLabel, anchor: .left)
dateLabel.pinToSuperview(edges: [.right],
constant: -Layout.spacer,
priority: .required)
dateLabel.pinTop(to: nameLabel, anchor: .bottom)
tableView.pinTop(to: dateLabel, anchor: .bottom)
tableView.pinToSuperview(edges: [.left, .right])
tableView.pinBottom(to: splitButton, anchor: .top)
splitButton.pinToSuperview(edges: [.left, .right, .bottom])
}
}
class CarouselTableViewDataSource: NSObject, UITableViewDataSource {
var items: [Item]? {
didSet {
let duplicatedItems = items
filteredItems = duplicatedItems?.filterDuplicates { ( $0.creationID ) }
}
}
private var filteredItems: [Item]!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredItems.count
}
//Set what each cell in the tableview contains.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CarouselTableViewCell = tableView.dequeueReusableCell(withIdentifier: "carouselTableViewCell") as! CarouselTableViewCell
let duplicateItems = items?.filter { Int($0.creationID)! == indexPath.row }
let quantity = "\(duplicateItems!.count) x "
let name = duplicateItems![0].name
let price = formatPrice(duplicateItems![0].price)
cell.quantityLabel.text = quantity
cell.nameLabel.text = name
cell.priceLabel.text = price
return cell
}
private func formatPrice(_ stringPrice: String) -> String {
var formattedPrice = stringPrice
let price = NSNumber(value: Double(stringPrice)!)
let formatter = NumberFormatter()
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: price) {
formattedPrice = "Tip Amount: \(formattedTipAmount)"
}
return formattedPrice
}
}
class CarouselTableViewCell: UITableViewCell {
var containerView = UIView()
var quantityLabel = UILabel()
var nameLabel = UILabel()
var priceLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "carouselTableViewCell")
setupHierarchy()
setupViews()
setupLayout()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
private func setupHierarchy() {
containerView.addSubview(quantityLabel)
containerView.addSubview(nameLabel)
containerView.addSubview(priceLabel)
containerView.addSubview(containerView)
contentView.addSubview(containerView)
}
private func setupViews() {
quantityLabel.textAlignment = .left
quantityLabel.backgroundColor = .clear
quantityLabel.textColor = Color.carouselText
nameLabel.textAlignment = .left
nameLabel.backgroundColor = .clear
nameLabel.textColor = Color.carouselText
priceLabel.textAlignment = .right
priceLabel.backgroundColor = .clear
priceLabel.textColor = Color.carouselText
}
private func setupLayout() {
containerView.pinToSuperview(edges: [.left, .right])
containerView.pinToSuperviewTop(withConstant: -2, priority: .required, relatedBy: .equal)
containerView.pinToSuperviewBottom(withConstant: -2, priority: .required, relatedBy: .equal)
quantityLabel.pinToSuperview(edges: [.top, .left, .bottom])
nameLabel.pinToSuperview(edges: [.top, .bottom])
nameLabel.pinLeft(to: quantityLabel, anchor: .right)
nameLabel.pinRight(to: priceLabel, anchor: .left)
priceLabel.pinToSuperview(edges: [.top, .right, .bottom])
}
}
Everything in the iCarousel view(cell) shows apart from the contents of the cell.
Any help or insight would be great