I'm creating custom UITableViewCells with a UILabel in each but can't get the label to display anywhere else than the top left corner of the cell, looks like this.
Constraints don't seem to be applied yet they're being called (reaching breakpoint). I tried to replace the UILabel by an UIImageView and apply the same constraints but nothing appears (i.e. table view cells are blank).
What am I missing?
View for cells:
import UIKit
class myTableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 16, weight: UIFontWeightLight)
label.text = "Sample"
label.backgroundColor = UIColor.red
return label
}()
func setupViews () {
addSubview(label)
//add constraints
let marginsGuide = self.contentView.layoutMarginsGuide
label.leadingAnchor.constraint(equalTo: marginsGuide.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: marginsGuide.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: marginsGuide.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: marginsGuide.bottomAnchor).isActive = true
}
}
View controller:
import UIKit
class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myTableView: UITableView = UITableView()
var myArray = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
//... do stuff incl. loading data into my myArray
let screenSize: CGRect = UIScreen.main.bounds
self.myTableView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
self.myTableView.delegate = self
self.myTableView.dataSource = self
self.myTableView.register(IngredientListTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(myTableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! myTableViewCell
return cell
}
}
Change this
addSubview(label)
to this
contentView.addSubview(label)
Related
I'm currently trying to put a tableview inside a tableview, but programmatically (in order to understand better wtf I'm doing in the storyboard way of doing this). I'm currently stuck at showing the inner table, any help please?
class ContactsViewController: UIViewController, UITabBarDelegate,
UITableViewDataSource, UITableViewDelegate {
private let contacts = ContactAPI.getData()
let contactsTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contactsTableView)
contactsTableView.translatesAutoresizingMaskIntoConstraints = false // enable Auto-Layout
contactsTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contactsTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contactsTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
contactsTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contactsTableView.dataSource = self
contactsTableView.delegate = self
contactsTableView.register(ContactsCell.self, forCellReuseIdentifier: "contactCell")
contactsTableView.rowHeight = 1000
contactsTableView.estimatedRowHeight = 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactsCell
cell.jobLabel.text = contacts[indexPath.row].jobTitle
cell.nameLabel.text = contacts[indexPath.row].name
return cell
}
}
The external TableViewCell is given by
class ContactsCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(image)
contentView.addSubview(nameLabel)
contentView.addSubview(jobLabel)
contentView.addSubview(tablello)
}
// working code that defines image, nameLabel, jobLabel
let tablello: UITableView = {
let tab = UITableView()
let data = innerTable()
tab.dataSource = data
tab.delegate = data
tab.frame = CGRect (x: 0, y: 0, width: 300, height: 300)
tab.register(gInnerCell.self, forCellReuseIdentifier: "gennaroCell")
tab.translatesAutoresizingMaskIntoConstraints = false
return tab
}()
}
And relative classes
class innerTable: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gennaroCell", for: indexPath) as! gInnerCell
cell.labelloNapoli.text = "\(indexPath.row)"
print("AO GENNĂ ")
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("loaded inner")
}
}
class gInnerCell: UITableViewCell {
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
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(labelloNapoli)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let labelloNapoli:UILabel = {
let lable = UILabel()
lable.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
return lable
}()
}
I've tried turning tablello into a separate View, and adding there the table as a subview, but (although it printed the inside print() ) it messes up all my UI.
In general what are you trying to accomplish by putting a tableView inside of another tableView? Are you able to display the same information using a custom tableViewCell? Or better yet use a CollectionView as they are very flexible when it comes to UI. Sorry for not answering the question; Just trying to get a better understanding of what you are trying to accomplish UI wise.
I'm new in autolayout programmatically, and I have a problem with displaying 2 different cells in UITableView as below:
The first cell I want to show profiles picture and the username, then the menu options. But as screenshot the second section menu option is displaying behind the first section (user profile picture and username).
How to solve this problem?
UserTableViewCell:
class UserTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
backgroundColor = .CustomGreen()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var userPhoto: UIImageView = {
let userPhoto = UIImageView()
userPhoto.translatesAutoresizingMaskIntoConstraints = false
userPhoto.layer.cornerRadius = userPhoto.frame.size.width / 2
userPhoto.clipsToBounds = true
return userPhoto
}()
lazy var username: UILabel = {
let username = UILabel()
username.textColor = .white
username.text = "Ahmed Abd Elaziz"
username.translatesAutoresizingMaskIntoConstraints = false
return username
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func setupUserPhotoConstraints() {
NSLayoutConstraint.activate([
userPhoto.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
userPhoto.widthAnchor.constraint(equalToConstant: frame.width / 3),
userPhoto.heightAnchor.constraint(equalToConstant: frame.width / 3)
])
}
func setupUsernameConstraints() {
NSLayoutConstraint.activate([
username.topAnchor.constraint(lessThanOrEqualTo: userPhoto.bottomAnchor, constant: 16),
username.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
])
}
func addSubviews() {
addSubview(containerView)
containerView.addSubview(userPhoto)
containerView.addSubview(username)
}
func layoutUI() {
addSubviews()
setupContainerViewConstraints()
setupUserPhotoConstraints()
setupUsernameConstraints()
}
}
SideMenuOptionTableViewCell:
class SideMenuOptionTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
backgroundColor = .CustomGreen()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var viewTitle: UILabel = {
let viewTitle = UILabel()
viewTitle.font = UIFont(name: "AvenirNext-Regular", size: 20)
viewTitle.textColor = .white
viewTitle.numberOfLines = 0
viewTitle.translatesAutoresizingMaskIntoConstraints = false
return viewTitle
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func setupFoodTitle() {
NSLayoutConstraint.activate([
viewTitle.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
viewTitle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16)
])
}
func addSubview() {
addSubview(containerView)
containerView.addSubview(viewTitle)
}
func layoutUI() {
addSubview()
setupContainerViewConstraints()
setupFoodTitle()
}
}
SideMenuTableViewController:
class SideMenuTableViewController: UITableViewController {
let viewControllers = ["Controller One", "Logout"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserTableViewCell")
tableView.register(SideMenuOptionTableViewCell.self, forCellReuseIdentifier: "SideMenuOptionTableViewCell")
tableView.reloadData()
tableView.backgroundColor = .CustomGreen()
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
// tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return section == 0 ? 1 : viewControllers.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserTableViewCell", for: indexPath) as! UserTableViewCell
cell.userPhoto.image = UIImage(named: "ahmed")
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "SideMenuOptionTableViewCell", for: indexPath) as! SideMenuOptionTableViewCell
cell.viewTitle.text = viewControllers[indexPath.row]
return cell
}
}
}
The problem here is you are adding constraints according to the cells height and all the views are being adjusted in the cell. As the space for the views is too small they are overlapping the views of the next cell. You can fix this problem by increasing the size of the content view of the cell.
Add this line in init of your UserTableViewCell
let minHeight = 70
let minHeightConstraint = contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
minHeightConstraint.priority = UILayoutPriority(rawValue: 999)
minHeightConstraint.isActive = true
I have a TableView which contains image stretched into cell and I added a view for darkening the image. Since I want to draw this view only in the beginning and not every time I scroll in table I added my codes into awakeFromNib in TableViewCell.
TableViewCell.swift
#IBOutlet weak var equipmentImageView: UIImageView!
let darkFilter = UIView()
override func awakeFromNib() {
super.awakeFromNib()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
My problem is that this view will not have the same width with the ImageView, so the effect I'm trying to implement takes half of the screen.
I followed this solution but the problem is that this solution will only apply after the cells recreate themselves by scrolling down and up again.
TableViewCell.swift
override func layoutSubviews() {
super.layoutSubviews()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
How can I apply this solution before the cells are created and will not redraw when scrolling through it?
You could set the cell's .background color to .black, and then lower the alpha of the equipmentImageView and you'd achieve the same darkened effect without having to add a new UIView.
EDIT
To illustrate my point: here's a really sloppy and quick example I drew up in Playground with an image titled "Untitled.jpg" to prove fading the image with a black cell background works the same as adding a faded black layer on top of a cell with an image:
Picture of result:
code:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class CustomTableViewCell: UITableViewCell {
let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
if let sample = Bundle.main.path(forResource: "Untitled", ofType: "jpg") {
let image = UIImage(contentsOfFile: sample)
imageView.image = image
}
imageView.clipsToBounds = true
return imageView
}()
let blackView: UIView = {
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .black
return myView
}()
let title: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = UIFont.systemFont(ofSize: 12)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .black
let views = [myImageView,
blackView,
title]
views.forEach {
contentView.addSubview($0)
NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalTo: contentView.heightAnchor),
$0.widthAnchor.constraint(equalTo: contentView.widthAnchor),
$0.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
$0.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCellViews(indexPath: IndexPath) {
blackView.alpha = 0
if indexPath.row % 2 == 0 {
if indexPath.row % 4 == 0 {
blackView.alpha = 0.4
title.text = "blackLayer added and faded"
} else {
backgroundColor = .black
myImageView.alpha = 0.6
title.text = "backView faded"
}
} else {
title.text = "not faded at all"
}
}
}
private let reuseId = "cellId"
class MyViewController : UIViewController {
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
return tableView
}()
let data = ["one", "two", "three", "four", "five"]
override func viewDidLoad() {
super.viewDidLoad()
setTableView()
}
func setTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: reuseId)
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.heightAnchor.constraint(equalTo: view.heightAnchor),
tableView.widthAnchor.constraint(equalTo: view.widthAnchor),
tableView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
}
extension MyViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) as! CustomTableViewCell
cell.setCellViews(indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
2 suggestions:
1) Since you are dealing with IBOutlets for the cell, you could also add the view on the IB on top of the image view, and then adding constrains to it to match up the image view size.
2) If you are aiming to adding it programmatically, you might need to setup its constraints based on the cell content view. For example:
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
contentView.addSubview(darkFilter)
darkFilter.translatesAutoresizingMaskIntoConstraints = false
darkFilter.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
darkFilter.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
darkFilter.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
darkFilter.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
I am creating a view using auto layout and the result is this.
After the view loads, then I grab some text data and fill the UITextView's. The "About me" item can be multiple lines, so I resize that specific frame. Then, I get the following.
You see how the about me textview is covering the next field? How can I resize the auto layout with the new about me textview size? I searched and found some suggestions to use setNeedsLayout and layoutIfNeeded, but none worked.
I am setting up the auto layout like the following:
inputsContainerView.addSubview(ageInput)
ageInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
ageInput.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
ageInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
ageInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(genderInput)
genderInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
genderInput.topAnchor.constraint(equalTo: ageInput.bottomAnchor).isActive = true
genderInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
genderInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(aboutInput)
aboutInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
aboutInput.topAnchor.constraint(equalTo: genderInput.bottomAnchor).isActive = true
aboutInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
aboutInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(memberSinceInput)
memberSinceInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
memberSinceInput.topAnchor.constraint(equalTo: aboutInput.bottomAnchor).isActive = true
memberSinceInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
memberSinceInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
After the view loads, I fetch data and resize the about me textview's frame using the following function:
func resizeTextView(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame
}
If I were you, I'd use UITableView to create this form and add the label and UITextView inside the cells.
You could do a UITableViewCell like below where I set the height of label to 60 and auto layout it. UITextView also using the auto layout and fitting the cell's bottom.
import UIKit
class UserDetailCell: UITableViewCell {
var userDetailLabel : UILabel = {
var label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.lightGray
return label
}()
var userDetailTextView : UITextView = {
var tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.isScrollEnabled = false
return tv
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
func setupUI(){
addSubview(userDetailLabel)
NSLayoutConstraint.activate([
userDetailLabel.topAnchor.constraint(equalTo: topAnchor),
userDetailLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailLabel.rightAnchor.constraint(equalTo: rightAnchor),
userDetailLabel.heightAnchor.constraint(equalToConstant: 60)
])
addSubview(userDetailTextView)
NSLayoutConstraint.activate([
userDetailTextView.topAnchor.constraint(equalTo: userDetailLabel.bottomAnchor),
userDetailTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailTextView.rightAnchor.constraint(equalTo: rightAnchor),
userDetailTextView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then your UIViewController should be like below. I'm setting a delegate to UITextView inside the cellForRowAt indexPath method. Since I set the delegate, the textViewDidChange delegate method will be called. It is written inside an extension.
import UIKit
class UserDetailsController: UITableViewController {
let cellId = "cell"
var person = Person(myAge: 20, myGender: "Male", aboutMe: "Hello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goaynge")
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserDetailCell.self, forCellReuseIdentifier: cellId)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserDetailCell
cell.userDetailTextView.delegate = self
cell.userDetailTextView.tag = indexPath.row
if indexPath.row == 0{
cell.userDetailLabel.text = "Age"
cell.userDetailTextView.text = "\(person.age)"
}else if indexPath.row == 1{
cell.userDetailLabel.text = "Gender"
cell.userDetailTextView.text = person.gender
}else if indexPath.row == 2{
cell.userDetailLabel.text = "About me"
cell.userDetailTextView.text = person.aboutMe
}
return cell
}
}
extension UserDetailsController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
if textView.tag == 2 {
person.aboutMe = textView.text
}else if textView.tag == 0 {
person.age = Int(textView.text) ?? 0
}else if textView.tag == 1 {
person.gender = textView.text
}
//this will keep the textview growing as we type
tableView.beginUpdates()
tableView.endUpdates()
}
}
Hope this helps you out. Your UI should look something like below.
I have a collection view inside a table view cell. The model is wish list items grouped by wish list name. There may be many wish lists. When a user selects wish lists they get a flat array of wish lists but I want to display a preview of the wish lists' item images in the cell. They are not individually selectable, a cell selection goes to a list of items for the selected wish list. Likewise, I will delete the entire wish list with a swipe delete motion.
My problem here is that the collectionView functions don't fire at all. The source data I'm testing with has one wish list and 4 items.
Here is my TableViewCell.
import Foundation
import UIKit
import Alamofire
import AlamofireImage
class WishListsCell: UITableViewCell {
var collectionView: UICollectionView!
let screenWidth = UIScreen.main.bounds.width
var alamofireRequest: Alamofire.Request?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenWidth, height: ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO))
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.estimatedItemSize.height = ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO)
layout.estimatedItemSize.width = screenWidth
collectionView = UICollectionView(frame: contentView.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(WishListsCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
self.contentView.addSubview(collectionView)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension WishListsCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
debugPrint(wishListItems.count)
return wishListItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WishListsCollectionViewCell", for: indexPath) as! WishListsCollectionViewCell
if let imageUrl = wishListItems[indexPath.row]["image"] as? String {
debugPrint(imageUrl)
cell.imageView.image = nil
if let image = ShoppingManager.sharedManager.photoCache.image(withIdentifier: imageUrl) {
cell.imageView.image = image
}
else
{
cell.alamofireRequest?.cancel()
cell.alamofireRequest = Alamofire.request(imageUrl)
.responseImage { response in
let img = response.result.value
cell.imageView.image = img
cell.imageView.contentMode = UIViewContentMode.scaleAspectFit
let imageWidth:CGFloat = self.screenWidth/4
let imageHeight:CGFloat = imageWidth * Constants.IMAGE_ASPECT_RATIO
cell.imageView.frame.size.width = imageWidth
cell.imageView.frame.size.height = imageHeight
ShoppingManager.sharedManager.photoCache.add(img!, withIdentifier: imageUrl)
}
}
}
return cell
}
}
Here is my CollectionViewCell:
import Foundation
import UIKit
import Alamofire
import AlamofireImage
class WishListsCollectionViewCell: UICollectionViewCell {
var alamofireRequest: Alamofire.Request?
var imageView: UIImageView
let screenWidth = UIScreen.main.bounds.width
override init(frame: CGRect) {
imageView = UIImageView()
super.init(frame: frame)
contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
NSLayoutConstraint.activate([
NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .width,
multiplier: 1.0, constant: screenWidth / 4),
NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal,
toItem: nil, attribute: .height,
multiplier: 1.0, constant: (screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are relevant parts of my ViewController code:
import UIKit
import Alamofire
import AlamofireImage
import MBProgressHUD
import DBAlertController
class WishListsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView = UITableView()
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
var cellId = "WishListsCell"
var didSelectItem:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.main.bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = .white
tableView.register(WishListsCell.self, forCellReuseIdentifier: cellId)
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let customView = UIView(frame: CGRect(x:0, y:0, width:screenWidth, height:30))
customView.backgroundColor = Constants.APP_BACKGROUND_COLOR
let button = UIButton(frame: CGRect(x: 0, y: 0, width: screenWidth, height: 30))
button.setTitle("Add New Wish List", for: .normal)
button.setTitleColor(Constants.APP_TEXT_COLOR, for: .normal)
button.titleLabel?.font = Constants.APP_HEADER_FONT
button.addTarget(self, action: #selector(addButtonAction), for: .touchUpInside)
customView.addSubview(button)
tableView.tableHeaderView = customView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return wishLists.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! WishListsCell
let listId = wishLists[indexPath.section]["listid"] as! Int
cell.alamofireRequest?.cancel()
cell.alamofireRequest = Alamofire.request(myURL)
.responseJSON(completionHandler: {
response in
self.parseWishListItemData(JSONData: response.data!)
})
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let listName = wishLists[section]["listname"] as! String
return listName
}
}
It doesn't look like you're adding your collection view to the view hierarchy after instantiating it in awakeFromNib. You need to call addSubview on the cell's content view.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenWidth, height: ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO))
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.estimatedItemSize.height = ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO)
layout.estimatedItemSize.width = screenWidth
collectionView = UICollectionView(frame: contentView.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
self.contentView.addSubview(collectionView) // <-- Need this
}
You may also have some issues if the table cell gets reused. You'll have to make sure that your collection view delegate gets set each time it's re-used--in prepareForReuse probably.
The init was incorrectly placed in awakeFromNib() and should have been in
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)