Changing items within custom UITableViewCell Class - swift

I have 2 classes - a UITableViewController and a custom UITableViewCell. I want to change the cell height for my UITableViewController, so I implement the following:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.frame.height*(1/12)
}
and it works! The cell height changes. Now, I go into my custom UITableViewCell class
import UIKit
class TableViewCell: UITableViewCell {
var time:UILabel = UILabel()
var name:UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) // the common code is executed in this super call
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.frame.width*0.22, height: self.frame.height))
imageView.image = UIImage(named: "Person")
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
self.addSubview(imageView)
let tap = UITapGestureRecognizer(target: self, action: #selector(signIn))
imageView.addGestureRecognizer(tap)
name = UILabel(frame: CGRect(x: self.frame.width*0.22, y: 0, width: self.frame.width*0.78, height: self.frame.height*0.7))
name.textColor = UIColor.gray
name.font = UIFont(name: name.font!.fontName, size: 30)
name.adjustsFontSizeToFitWidth = true
self.addSubview(name)
time = UILabel(frame: CGRect(x: self.frame.width*0.22, y: self.frame.height*0.65, width: self.frame.width*0.78, height: self.frame.height*0.3))
time.textColor = UIColor.gray
time.font = UIFont(name: time.font!.fontName, size: 15)
time.adjustsFontSizeToFitWidth = true
self.addSubview(time)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#objc func signIn(tap: UITapGestureRecognizer) {
let tappedImage = tap.view as! UIImageView
tappedImage.image = UIImage(named: "PersonClocked")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
So, I now edit the function inside my controller to call the cells.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.layer.borderColor = UIColor.red.cgColor
cell.layer.borderWidth = 1
cell.name.text = names[indexPath.row]
cell.time.text = times[indexPath.row]
return cell
}
I expect it to all work now! But, it doesn't. For whatever reason, the methods get called in this order...
tableview(cellForRowAt) -> TableViewCell(override init) -> tableView(heightForRowAt)'
So, when I go to run it, it looks something like below. The cell is created with Swift's/Apple's default runtime tableView, then the cell size is changed, but everything inside the cell is still the size of the original default value. I need it to be the size of the cell. Any ideas?
Note - I added a border so you could see the size of the cell compared to the items.

Frame layout can't help in creating dynamic height tables you have to use dynamic tableViewCells and create the cell's contentView with constraints to get the height change with the current content
//
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) // the common code is executed in this super call
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.frame.width*0.22, height: self.frame.height))
imageView.image = UIImage(named: "Person")
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
self.addSubview(imageView)
let tap = UITapGestureRecognizer(target: self, action: #selector(signIn))
imageView.addGestureRecognizer(tap)
name = UILabel(frame: CGRect(x: self.frame.width*0.22, y: 0, width: self.frame.width*0.78, height: self.frame.height*0.7))
name.textColor = UIColor.gray
name.font = UIFont(name: name.font!.fontName, size: 30)
name.adjustsFontSizeToFitWidth = true
self.addSubview(name)
time = UILabel(frame: CGRect(x: self.frame.width*0.22, y: self.frame.height*0.65, width: self.frame.width*0.78, height: self.frame.height*0.3))
time.textColor = UIColor.gray
time.font = UIFont(name: time.font!.fontName, size: 15)
time.adjustsFontSizeToFitWidth = true
self.addSubview(time)
imageView.translatesAutoresizingMaskIntoConstraints = false
name.translatesAutoresizingMaskIntoConstraints = false
time.translatesAutoresizingMaskIntoConstraints = false
imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor,constant: 0).isActive = true
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 20).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 30).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 30).isActive = true
name.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 20).isActive = true
name.leadingAnchor.constraint(equalTo: imageView.leadingAnchor,constant: 20).isActive = true
name.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: -20).isActive = true
time.topAnchor.constraint(equalTo: name.topAnchor,constant: 20).isActive = true
time.leadingAnchor.constraint(equalTo: imageView.leadingAnchor,constant: 20).isActive = true
time.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: -20).isActive = true
time.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant: -20).isActive = true
}
//
put this in viewDidLoad
tableview.estimatedRowHeight = 100
tableview.rowHeight = UITableViewAutomaticDimension
and don't implement heightForRowAt method

Related

Dinamically fill UITableViewCell with buttons side by side

Continuing to resolve the task of UITableViewCell, I could fix all UIButtons side by side but they are not appearing properly in executing time. Sometimes the second row mix with the third.
I tryied with cell.layoutIfNeeded() but still wrong.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DetailViewCell
let array = sections[indexPath.section].filters
var hStackView = UIStackView()
var vStackView = UIStackView()
vStackView.axis = .vertical
vStackView.distribution = .fillEqually
vStackView.spacing = 12
vStackView.alignment = .top
for (index, item) in array.enumerated() {
let btn = UIButton()
btn.setTitle(item.title, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 12)
btn.setTitleColor(.black, for: .normal)
btn.backgroundColor = UIColor(red: 220, green: 220, blue: 220)
btn.layer.cornerRadius = 15;
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
btn.tag = index
if index % 2 == 0 {
hStackView = UIStackView()
hStackView.axis = .horizontal
hStackView.spacing = 12
hStackView.alignment = .fill
hStackView.distribution = .fillEqually
hStackView.addArrangedSubview(btn)
if (index + 1) == array.count {
vStackView.addArrangedSubview(hStackView)
hStackView.leadingAnchor.constraint(equalTo: vStackView.leadingAnchor).isActive = true
hStackView.widthAnchor.constraint(equalTo: vStackView.widthAnchor, multiplier: 0.5).isActive = true
}
}
else {
hStackView.addArrangedSubview(btn)
vStackView.addArrangedSubview(hStackView)
hStackView.leadingAnchor.constraint(equalTo: vStackView.leadingAnchor).isActive = true
hStackView.trailingAnchor.constraint(equalTo: vStackView.trailingAnchor).isActive = true
}
}
cell.addSubview(vStackView)
vStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cell.leadingAnchor.constraint(equalTo: vStackView.leadingAnchor, constant: -8),
cell.trailingAnchor.constraint(equalTo: vStackView.trailingAnchor, constant: 8),
cell.topAnchor.constraint(equalTo: vStackView.topAnchor, constant: -8),
cell.bottomAnchor.constraint(equalTo: vStackView.bottomAnchor, constant: 8)
])
cell.layoutIfNeeded()
return cell
You should look up information about dequeueReusableCell
It works somehow like this: when you get a cell in tableView(_: cellForRowAt:) from dequeueReusableCell you are provided with a ready-made cell that was used somewhere. It follows from this that all content that the cells differ in must be placed in the cell only in this method
Note, the button with a label сссс is clearly from the top cell
I would advise you to declare in your UITableViewCell subclass a method in which you put content inside and call it every time inside tableView(_:cellForRowAt:)
You can use a storyboard if you like, but if you want to do it with code, I can suggest custom UITableViewCell subclass template:
class CustomTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
// MARK: - Public methods
func setContent(_ content: <# ContentType #>) {
}
// MARK: - Private setup methods
private func setupView() {
makeConstraints()
}
private func makeConstraints() {
NSLayoutConstraint.activate([
])
}
}

Self sizing dynamic UICollectionViewCell height improperly sizes itself after calling reloadData()

I have a UICollectionView that sizes cells heights automatically so depending on how much text is inside the cell it will size the height appropriately.
This works perfectly fine I can click all the buttons, scroll up or down, etc, but the problem is that when I call reloadData(), the collectionViewCell's constraints get screwed up and they stack on top of each other for some reason.
Here is a picture of the collectionView before reloadData() is called:
Here is a picture of the collectionVIew after I call reloadData():
Anyone possibly know why this is happening and how I can fix it?
Here is my code for the CustomCollectionView:
class CustomCollectionView: UICollectionView {
public let bottomRefresh = CollectionViewBottomRefresh()
init() {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .vertical
layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 50)
super.init(frame: .zero, collectionViewLayout: layout)
alwaysBounceVertical = true
backgroundColor = .systemBackground
delaysContentTouches = false
showsVerticalScrollIndicator = false
register(PostView.self, forCellWithReuseIdentifier: "post")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesShouldCancel(in view: UIView) -> Bool {
if view is UIButton || view is UITextField {
return true
}
return super.touchesShouldCancel(in: view)
}
}
Here is my code for CollectionViewCell:
class PostView: UICollectionViewCell, {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(commentsButton)
contentView.addSubview(kuduAppTeamDeleteButton)
contentView.addSubview(titleLabel)
contentView.addSubview(infoButton)
contentView.addSubview(imageViewButton)
contentView.addSubview(likeButton)
contentView.addSubview(followButton)
contentView.addSubview(profile)
contentView.addSubview(likeCount)
contentView.addSubview(date)
contentView.addSubview(line)
addConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func setupView(post: Post) {
guard let post = fb.posts.firstIndex(where: { p in p.id == post.id }) else { return }
self.post = post
guard let user = fb.users.firstIndex(where: { user in user.id == fb.posts[post].uid }) else { return }
self.user = user
if fb.currentUser.likes.contains(fb.posts[post].id) {
self.likeButton.setImage(UIImage(systemName: "hand.thumbsup.fill"), for: .normal)
self.likeButton.tintColor = UIColor.theme.blueColor
} else {
self.likeButton.setImage(UIImage(systemName: "hand.thumbsup"), for: .normal)
self.likeButton.tintColor = .label
}
let result = String(format: "%ld %#", locale: Locale.current, fb.posts[post].likeCount, "")
likeCount.text = result
//Button Actions
infoButton.addAction(infoButtonAction, for: .touchUpInside)
likeButton.addAction(likeButtonAction, for: .touchUpInside)
followButton.addAction(followButtonAction, for: .touchUpInside)
imageViewButton.addAction(imageViewButtonAction, for: .touchUpInside)
profile.addAction(profileAction, for: .touchUpInside)
commentsButton.addAction(commentsButtonAction, for: .touchUpInside)
kuduAppTeamDeleteButton.addAction(kuduAppTeamDeleteButtonAction, for: .touchUpInside)
//Date
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .long
let dateString = dateFormatter.string(from: fb.posts[post].date)
date.text = dateString
//Set follow button text
if self.fb.currentUser.following.contains(fb.users[user].id) {
self.followButton.label.text = "Unfollow"
} else {
self.followButton.label.text = "Follow"
}
//Set imageview image
imageViewButton.setImage(fb.posts[post].image, for: .normal)
imageViewButton.imageView!.contentMode = .scaleAspectFill
//Set user image
profile.usernameLabel.text = fb.users[user].username
profile.profileImage.image = fb.users[user].profileImage
if profile.profileImage.image == UIImage(systemName: "person.circle.fill") {
profile.profileImage.tintColor = UIColor.theme.accentColor
}
//Set post title
titleLabel.text = fb.posts[post].title
}
override func prepareForReuse() {
//Remove all actions
infoButton.removeAction(infoButtonAction, for: .touchUpInside)
likeButton.removeAction(likeButtonAction, for: .touchUpInside)
imageViewButton.removeAction(imageViewButtonAction, for: .touchUpInside)
profile.removeAction(profileAction, for: .touchUpInside)
commentsButton.removeAction(commentsButtonAction, for: .touchUpInside)
followButton.removeAction(followButtonAction, for: .touchUpInside)
kuduAppTeamDeleteButton.removeAction(kuduAppTeamDeleteButtonAction, for: .touchUpInside)
//Remove any other text or images
for subview in imageViewButton.subviews {
if let subview = subview as? UIImageView, subview.image == UIImage(systemName: "play.circle.fill") {
subview.removeFromSuperview()
}
}
imageViewButton.setImage(nil, for: .normal)
kuduAppTeamDeleteButton.color = nil
profile.profileImage.image = nil
titleLabel.text = nil
date.text = nil
self.followButton.label.text = nil
}
// Sets a requried width and a dynamic height that changes depending on what is in the cell. So we can have searchbar as first cell heigh 50, and post in other cells with height of view.bounds.width.
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
private func addConstraints() {
kuduAppTeamDeleteButton.height(30)
kuduAppTeamDeleteButton.width(UIScreen.main.bounds.width / 4)
kuduAppTeamDeleteButton.bottom(to: commentsButton)
kuduAppTeamDeleteButton.leftToRight(of: commentsButton, offset: 5)
imageViewButton.width(UIScreen.main.bounds.width)
imageViewButton.height(UIScreen.main.bounds.width * 9/16)
imageViewButton.topToSuperview()
infoButton.leftToRight(of: titleLabel, offset: 6)
infoButton.topToBottom(of: imageViewButton, offset: 15)
infoButton.width(30)
infoButton.height(30)
titleLabel.horizontalToSuperview(insets: UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 40))
titleLabel.topToBottom(of: imageViewButton, offset: 5)
titleLabel.height(min: 50)
likeButton.topToBottom(of: titleLabel, offset: 10)
likeButton.trailingToSuperview(offset: 10)
likeButton.height(32)
likeButton.width(32)
profile.leadingToSuperview(offset: 5)
profile.topToBottom(of: titleLabel, offset: 10)
profile.widthToSuperview(multiplier: 0.4)
likeCount.trailingToLeading(of: likeButton, offset: -5)
likeCount.topToBottom(of: titleLabel, offset: 15)
followButton.topToBottom(of: titleLabel, offset: 5)
followButton.trailingToLeading(of: likeCount, offset: -10)
followButton.height(50)
followButton.width(UIScreen.main.bounds.width / 4)
date.bottom(to: commentsButton, offset: -5)
date.trailingToSuperview(offset: 5)
commentsButton.topToBottom(of: profile, offset: 10)
commentsButton.leadingToSuperview(offset: 5)
line.horizontalToSuperview()
line.bottom(to: commentsButton)
line.height(1)
contentView.bottom(to: line)
contentView.widthToSuperview()
}
}
Thanks in advance!
Finally figured how you can fix this problem if anyone else is having this issue.
So what I did was I decided to use a UITableView instead of a UICollectionView. When you change to a UITableView you can access the variableUITableView.automaticDimension.
Side note: You can only use a UITableView if you have one column.
So this is what I did for my UITableView:
class MyTableView: UITableView {
public let bottomRefresh = TableViewBottomRefresh()
init() {
super.init(frame: .zero, style: .plain)
rowHeight = UITableView.automaticDimension
estimatedRowHeight = 500
separatorStyle = .none
allowsSelection = false
delaysContentTouches = false
alwaysBounceVertical = true
showsVerticalScrollIndicator = false
register(MyTableViewCell.self, forCellReuseIdentifier: "MyCell")
}
}
Then you need to create a containerView that you can later put all the cells contents inside:
private let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Add contentView.addsubview(containerView) and put all cell contents inside the containerView so your cell can automatically size itself:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(containerView)
containerView.addSubview(profileImage)
containerView.addSubview(username)
containerView.addSubview(editProfileButton)
addConstraints()
}
addconstraints() func :
private func addConstraints() {
profileImage.topToSuperview()
profileImage.centerXToSuperview()
profileImage.widthToSuperview(multiplier: 1/2)
profileImage.height(UIScreen.main.bounds.width / 2)
username.topToBottom(of: profileImage, offset: 10)
username.horizontalToSuperview()
username.height(50)
editProfileButton.height(50)
editProfileButton.widthToSuperview(multiplier: 1/3)
editProfileButton.centerXToSuperview()
editProfileButton.topToBottom(of: username, offset: 10)
containerView.widthToSuperview()
containerView.bottom(to: editProfileButton)
}
Hopefully this helps someone! If anyone ever figures out how to call reloadData() with a dynamic collectionViewCell height without screwing up the cell constraints let me know! I tried for 2 weeks and still never found a solution :(.

CollectionView Cell Changing Size when screen reloads

I am creating a screen where the user can search for films and the results load in a collection View, everything loads perfectly but in the simulator when I clicked "command, shift, A" to change to light mode to make sure the colours would adapt correctly the Cells randomly changed size to full screen instead of what I have set.
This also happens when I leave the application for the home page and then click back into the app. I am creating everything programmatically so would need answer this way please.
Below is the code from my custom cell:
import UIKit
class FavouritesCell: UICollectionViewCell {
static let identifier = "FavouritesCell"
let movieTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.lineBreakMode = .byWordWrapping
label.textColor = .secondaryLabel
//label.text = "Title"
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
label.font = label.font.withSize(12)
label.minimumScaleFactor = 0.75
return label
}()
let image : UIImageView = {
let image = UIImageView()
image.clipsToBounds = true
image.translatesAutoresizingMaskIntoConstraints = false
//image.backgroundColor = .yellow
image.layer.cornerRadius = 10.0
return image
}()
let cancelItem : UILabel = {
let label = UILabel()
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor.systemGray.cgColor
label.text = "X"
label.textAlignment = .center
label.layer.cornerRadius = 15
//label.isHidden = true
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(image)
contentView.addSubview(movieTitle)
contentView.addSubview(cancelItem)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
image.frame = CGRect(x: 0, y: 25, width: contentView.width, height: contentView.height - 30 )
movieTitle.frame = CGRect(x: 0, y: 0, width: contentView.width, height: 25)
cancelItem.frame = CGRect(x: image.right - 25, y: image.top , width: 25, height: 25)
}
}
Here is the code from the view controller in relation to the collectionView:
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 2
layout.minimumInteritemSpacing = 2
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(FavouritesCell.self, forCellWithReuseIdentifier: FavouritesCell.identifier)
collectionView.clipsToBounds = true
collectionView.backgroundColor = UIColor.systemBackground
return collectionView
}()
NSLayoutConstraint.activate([
searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
searchText.heightAnchor.constraint(equalToConstant: 50),
searchText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchText.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: searchText.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.width / 3) - 4, height: (view.width / 2) - 2 )
}
image of CollectionView working normally
After the screen has been reloaded
Try to give your imageView a fixed height and fixed width and try it again, It will work

How to fix UITableView not displaying in UIStackView

I followed a tutorial to build a weather app programmatically (without a storyboard) that displays the current city and temperature. I am modifying it to display a 5 day forecast instead of just the current temperature by adding a UITableView, but it is not showing up.
Here is my WeatherView code:
class WeatherView: UIView, UITableViewDelegate, UITableViewDataSource {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
setupViews()
setupConstraints()
}
private func setupViews() {
self.addSubview(mainStack)
conditionsImageStack.addArrangedSubview(conditionsImageView)
mainStack.addArrangedSubview(conditionsImageStack)
// forecastTable.register(UITableView.self, forCellReuseIdentifier: "forecast")
forecastTable.register(UITableViewCell.self, forCellReuseIdentifier: "forecast")
forecastTable.delegate = self
forecastTable.dataSource = self
mainStack.addArrangedSubview(centerContentStack)
// centerContentStack.addArrangedSubview(temperatureLabel)
centerContentStack.addArrangedSubview(forecastTable)
centerContentStack.addArrangedSubview(cityAndConditionsStack)
cityAndConditionsStack.addArrangedSubview(cityLabel)
cityAndConditionsStack.addArrangedSubview(conditionsLabel)
mainStack.addArrangedSubview(buttonsStack)
buttonsStack.addArrangedSubview(celsiusButton)
buttonsStack.addArrangedSubview(fahrenheitButton)
buttonsStack.addArrangedSubview(UIView(frame: .zero))
}
private func setupConstraints() {
mainStack.pinEdges(to: self)
}
let mainStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 10
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 30, bottom: 30, right: 30)
return stackView
}()
let conditionsImageStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.alignment = .trailing
stackView.spacing = 10
return stackView
}()
let cityAndConditionsStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 10
return stackView
}()
let centerContentStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.alignment = .center
stackView.spacing = 60
return stackView
}()
// TABLE CODE HERE
var animalArray : [String] = ["elephant", "pig", "goat"]
var forecastTable: UITableView = {
let tableView = UITableView()
let estimatedHeight = tableView.numberOfRows(inSection: 3) //You may need to modify as necessary
// let width = parentView.frame.size.width
let width = estimatedHeight
tableView.frame = CGRect(x: 0, y: 0, width: width, height: estimatedHeight)
return tableView
}()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = forecastTable.dequeueReusableCell(withIdentifier: "forecast")
cell?.textLabel!.text = "Success"
return cell!
}
let temperatureLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 144)
label.textColor = .white
label.textAlignment = .center
label.text = "18°"
return label
}()
let cityLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 36)
label.textColor = .white
label.textAlignment = .center
label.text = "Atlanta"
return label
}()
let conditionsLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 20)
label.textColor = .white
label.textAlignment = .center
label.text = "Sunny"
return label
}()
let conditionsImageView: UIImageView = {
let image = UIImage(named: "sun")
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
imageView.widthAnchor.constraint(equalToConstant: image!.size.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: image!.size.height).isActive = true
return imageView
}()
let celsiusButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("°C", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 73)
button.setTitleColor(.white, for: .normal)
return button
}()
let fahrenheitButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("°F", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 73)
button.setTitleColor(.white, for: .normal)
return button
}()
let buttonsStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .horizontal
stackView.distribution = .equalCentering
stackView.spacing = 10
return stackView
}()
}
Here is my ViewController code:
class WeatherViewController: UIViewController {
var mainView: WeatherView! { return self.view as! WeatherView }
let presenter: WeatherPresenter!
init(with presenter: WeatherPresenter){
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
updateBackground()
}
func updateBackground() {
self.mainView.updateGradient(presenter.backgroundColor)
}
override func loadView() {
self.view = WeatherView(frame: UIScreen.main.bounds)
}
}
Here is my UIView + Constraints code:
extension UIView {
func pinEdges(to view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
self.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
I have tried forcing it to display by setting the dimensions with tableView.frame = CGRect(x: 0, y: 0, width: width, height: estimatedHeight) but that did not work.
I am registering the table in the view class instead of of the view controller class, but I am not sure if this is the problem or how to modify it correctly.
You're doing a couple things wrong...
First, in your var forecastTable: UITableView = {...} declaration, you have a line:
let estimatedHeight = tableView.numberOfRows(inSection: 3)
But, at that point, the table view has no dataSource -- even if it did, your dataSource has only section. So the value returned is undefined. If Iprint(esttmatedHeight)I get9223372036854775807. So you are trying to set the frame of your table view to9223372036854775807 x 9223372036854775807`
Next, when you add a table view to a stack view, the stack view will try to arrange it based on the stack view's properties and the table view's intrinsic size. However, the table view has no intrinsic size at that point - you must use auto-layout properties.
So, remove the frame setting for your table view, and after you've added it to the stack view, use:
// I'm guessing you want a height based on number of rows * row height?
// so, something like:
let estimatedHeight = CGFloat(3 * 44)
forecastTable.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
forecastTable.widthAnchor.constraint(equalTo: centerContentStack.widthAnchor),
forecastTable.heightAnchor.constraint(equalToConstant: estimatedHeight),
])
That will make the table view the same width as the centerContentStack that holds it, and give it a height.
At this point, you should see your table.
You are adding mainStack to the superView but not specifying it's dimensions. You need to set constrains to mainStack similar this,
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
mainStack.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
Above code fills the entire super view with mainStack. You can modify this code according to your requirement.

Swift: tableview cell change width and postion

I create chatroom
I want user 1 chats position in right and user 2 chats position in left like below image
My code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyChatCell
var frame = cell.frame
let newWidth = frame.width * 0.50
let space = (frame.width - newWidth) / 2
frame.size.width = newWidth
frame.origin.x += space
cell.frame = frame
return cell
}
But my code not work
Please consider using Anchor for your MyChatCellLeft and MyChatCellRight and anchor each example:
cell.messageLabel = "My Message"
cell.floatPosition = true // true left / false right
I must mention I haven't tested this. but you can try it and take it as a blueprint for what you are doing.. you also need to add some kind of logic of who is who... example user1 left user2 right. that depends on your logic...
class ChatBubbleCell: UITableViewCell{
var position: Bool = false
let messageLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var floatPosition = Bool() {
didSet {
if(floatPosition == true){
position = true
} else {
position = false
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(){
addSubview(messageLabel)
// lef position
if position == true {
let constrains = [messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 15),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15),
messageLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 20)
]
NSLayoutConstraint.activate(constrains)
} else {
let constrains = [messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 15),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15),
messageLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -30)
]
NSLayoutConstraint.activate(constrains)
}
}
}
Consider the cell to be the whole row and use a subview to update frames.
Update the frames on the method of will display cell for row at index path