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

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 :(.

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

The size of the button on the stack

I am trying to make my own button size on the stack.
(width: 350, height: 50)
To create a button, I wrote an extension.
But for some reason it does not work out to create in the required size on the stack.
What did I miss?
final class ChooseLanguageView: UIView {
private let ruButton = UIButton.createSystemButton(withTitle: "ru")
private let enButton = UIButton.createSystemButton(withTitle: "en")
private let deButton = UIButton.createSystemButton(withTitle: "de")
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [ruButton,
enButton,
deButton])
stackView.axis = .vertical
return stackView
}()
private func setupStackView() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
setupStackView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
// MARK: - extension
private extension UIButton {
static func createSystemButton(withTitle title: String) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.backgroundColor = .white
button.frame.size = CGSize(width: 350, height: 50) // Here I am trying to adjust the size
return button
}
}
Here's what comes out:
You need to add button width and height as constraints
private extension UIButton {
static func createSystemButton(withTitle title: String) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.backgroundColor = .white
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 350),
button.heightAnchor.constraint(equalToConstant: 50)
])
return button
}
}

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.

Changing items within custom UITableViewCell Class

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

UIViewController as rootViewController of UINavigationController causes root views buttons to move

When I do this in AppDelegate:
window?.rootViewController = {
let mainController = MenuViewController()
return mainController
}()
I get this:
But when I do this in AppDelegate:
window?.rootViewController = {
let mainController = UINavigationController(rootViewController: MenuViewController())
return mainController
}()
I get this:
Why and how do I fix? Please specify which information if more information is needed.
Here is the MenuView code that lays out the buttons manually and also sets up the properties of the buttons:
class MenuView: UIView {
//title
let titleLabel: UILabel = {
let label = UILabel()
label.text = "Survive The Attackers!!"
label.backgroundColor = UIColor.white
return label
}()
//set up buttons
let newGameButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("New Game", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
let resumeButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Resume Game", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
let highScoresButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("High Scores", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
//add subviews and initialize the view
override init(frame: CGRect){
super.init(frame: frame)
self.backgroundColor = UIColor(patternImage: UIImage(named: "background1.png")!)
addSubview(titleLabel)
addSubview(newGameButton)
addSubview(resumeButton)
addSubview(highScoresButton)
}
required init?(coder aDecoder: NSCoder) {
fatalError("It's Apple. What did you expect?")
}
//manually layout the main menu
override func layoutSubviews() {
var cursor: CGPoint = .zero
let buttonHeight = CGFloat(40.0);
let buttonWidth = CGFloat(160.0);
let labelWidth = buttonWidth + 20;
let spacing = bounds.height/4
let titleY = 2/3 * spacing
cursor.y = titleY
cursor.x = bounds.width/2 - labelWidth/2
titleLabel.frame = CGRect(x: cursor.x, y: cursor.y, width: labelWidth, height: buttonHeight)
cursor.y = spacing
cursor.x = bounds.width/2 - buttonWidth/2
newGameButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
cursor.y += spacing
resumeButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
cursor.y += spacing
highScoresButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
}
The buttons are laid out manually in layoutSubviews
Here is my MenuView controller code:
class MenuViewController: UIViewController {
var delegateID: String = UUIDVendor.vendUUID()
private var menuView: MenuView {
return view as! MenuView
}
init(){
super.init(nibName: nil, bundle: nil)
//edgesForExtendedLayout = .init(rawValue: 0)
}
required init?(coder aDecoder: NSCoder){
fatalError()
}
//loads the view in and sizes it correctly
override func loadView() {
view = MenuView()
//extendedLayoutIncludesOpaqueBars = false
}
override func viewDidLoad() {
menuView.newGameButton.addTarget(self, action: #selector(MenuViewController.newGameButtonTapped(button:)), for: .touchUpInside)
menuView.resumeButton.addTarget(self, action: #selector(MenuViewController.resumeGameButtonTapped(button:)), for: .touchUpInside)
menuView.highScoresButton.addTarget(self, action: #selector(MenuViewController.highScoreButtonTapped(button:)), for: .touchUpInside)
menuView.setNeedsLayout()
}
//fuction that handles the event when the newGameButton is tapped
#objc func newGameButtonTapped(button: UIButton){
//reset the data in the model somehow
navigationController?.pushViewController(GameViewController(), animated: true)
}
//function that handles the event when the resume game button is tapped
#objc func resumeGameButtonTapped(button: UIButton){
}
//function that handels the event when the high scores button is tapped
#objc func highScoreButtonTapped(button: UIButton){
}
call super for layoutSubviews
private var menuView: MenuView = {
let vw = MenuView()
return vw
}()
override func viewDidLoad() {
super.viewDidLoad()
view = MenuView() //Add here
//Your code
}
And remove loadView() from MenuViewController