UITableView setting row Height not having effect - ios

Why is it that when I set tableView.rowHeight = 100 in viewDidLoad() I always get the default height value of 44.0? I tried setting tableView.estimatedHeight =100 as well but no luck, I tried setting the delegate method tableView.heightForRowAt as well but that doesn't seem to have any effect as well what so ever. So the question is: how do you set the height for a tableView Cell?
override func viewDidLoad(){ // tableView viewDidLoad
super.viewDidLoad()
tableView.rowHeight = 100
tableView.register(TickerCell.self, forCellReuseIdentifier: "Cell")
}
// Custom Cell Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
print(frame.height) // always prints 44.0
let symbolstack = UIStackView(arrangedSubviews: [symbolLabel,companyLabel])
let sectorstack = UIStackView(arrangedSubviews: [sectorLabel,exchangeLabel])
let mainStack = UIStackView(arrangedSubviews: [symbolstack,sectorstack])
sectorstack.axis = .vertical
symbolstack.axis = .vertical
sectorstack.alignment = .trailing
symbolstack.alignment = .leading
symbolstack.distribution = .fillEqually
sectorstack.distribution = .fillEqually
mainStack.distribution = .fillProportionally
addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -45).isActive = true
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15).isActive = true
addSubview(WatchlistStar)
WatchlistStar.translatesAutoresizingMaskIntoConstraints = false
WatchlistStar.widthAnchor.constraint(equalToConstant: 15).isActive = true
WatchlistStar.leadingAnchor.constraint(equalTo: mainStack.trailingAnchor, constant: 20).isActive = true
WatchlistStar.heightAnchor.constraint(equalToConstant: 15).isActive = true
WatchlistStar.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
So the problem appears to be in the fact then in the cell init method the rowHeight I set In viewDidLoad does not seem to register, only in prepareForReuse when I print frame.height do I get 100 printed,So in which method do I setup the cell layout then?

A cell is not born knowing its height, so init is a pointless place to look at the frame. In fact, it has no inherent height. Cells are reused. They take on a height only in relation to a particular row of the table where they are being used right at the moment. That height can change when the cell is used in a different row (because rows can be different heights). So your layout needs to cope with that.
In the code you have shown, you are using autolayout. The whole point of autolayout is that you don't care about the frame of things at any one moment. Everything adjusts automatically as the surrounding frame changes. Autolayout is about relationships.
So the solution in that case is: don't look at frame.height. You don't need to know it. Just lay out the relationships between the views and the cell, and everything will be correct when the cell appears, if you have used autolayout correctly.
On the other hand, as you now say in a comment "Im forced to [use autolayout] not because I want to" — okay, so if the goal is to do layout manually, like we did before there was autolayout, then the place to do it is in the data source's cellForRowAt:. Or you could try doing it in the cell's layoutSubviews if you want the cell to lay itself out. See the old edition of my book, online, for how we used to do this: http://www.apeth.com/iOSBook/ch21.html#_custom_cells
Be very careful to distinguish between adding subviews and resizing them. You don't want to make the mistake of adding subviews that you have already added. So add the subviews in init, sure, as it is called only once, but size them in a place where the actual size of the row has been communicated to the cell.
One more piece of advice. Your current code uses the phrase addSubview, meaning self.addSubview. That is totally wrong and illegal. Never never add a subview directly to a cell. Add it only to the cell's contentView, and size it in relation to that.

override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 40
}

Related

How to show UITableView below a UITextField using swift programmatically?

So I have this UITextField and I want to show a UITableView just below it. I'm trying to show the suggestions in the UITableView my logic works fine but I want to know how can I show the UITableView beneath the UITextField.
Currently, I'm doing something like this
private func setUpAutoCompleteTable() {
autocompleteTableView = UITableView(frame: CGRect(x: self.originTextField.bounds.minX,y: self.originTextField.bounds.maxY,width: self.originTextField.bounds.width,height: self.originTextField.bounds.height * 4), style: UITableView.Style.plain)
self.view.addSubview(autocompleteTableView)
autocompleteTableView.delegate = self
autocompleteTableView.dataSource = self
autocompleteTableView.isScrollEnabled = true
autocompleteTableView.isHidden = true
autocompleteTableView.register(LocationAutoCompleteCell.self, forCellReuseIdentifier: "AutoCompleteRowIdentifier")
}
But it is showing the table at the top of the screen. I want to tell you that originTextField is inside another view.
UPDATE
This is how the view hierarchy looks.
So, to layout the tableView programmatically you need to declare it in your ViewController
var autoCompleteTableView: UITableView!
Initialize it in ViewDidLoad
autoCompleteTableView = UITableView(frame: .zero) // constraints will set the frame for us later
set constraints in ViewDidLoad
NSLayoutConstraint.activate([
autoCompleteTableView.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 5), //the constant is how many pts below the textField you want the tableView to be
autoCompleteTableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
autoCompleteTableView.widthAnchor.constraint(equalTo: view.widthAnchor),
autoCompleteTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
and then you can set whatever tableView properties you need to. remember, your VC also needs to implement UITableViewDelegate and UITableViewDataSource
You should probably go for Gabe's answer with a detail, considering that the textfield is inside another view.
So, you'll need to get the textfield's bottom anchor reference from the Storyboard. You can do that by control dragging the constraint to your swift file.
Then you can do this:
autoCompleteTableView.topAnchor.constraint(equalTo: self.textFieldBottomAnchorReference, constant: 5)

UITableViewCell constraints error

I have a view different UITableViewCells, depending on the content one of those cells will be loaded, however 1 cell is messing with me.
I'm trying to add it to the Table View, but my constraints are messing with me. With the translatesAutoresizingMaskIntoConstraints on true it displays fine, however I get the well known error message regarding constraints. Setting translatesAutoresizingMaskIntoConstraints to false, seems to completely mess up the layout, especially height wise.
class videoCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
playerView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func playerView() {
let height = self.frame.width * 9 / 16
let vpFrame = CGRect(x: 0, y: 0, width: self.frame.width, height: height)
let vpView = vpView(frame: vpFrame)
//vpView.translatesAutoresizingMaskIntoConstraints = false
vpView.sizeToFit()
addSubview(vpView)
vpView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
vpView.rightAnchor.constraint(equalTo: rightAnchor, constant: -10).isActive = true
vpView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
vpView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
}
}
The error with translatesAutoresizingMaskIntoConstraints on false:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want.
I also have this one set:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
The problem here is you mixing Frame-based layout rules and Autolayout rules.
A frame based layout is what you did in your code by calculating the size and the width of your vpview.
The Autolayout constraints are what you did by activating some anchor constraints for location attributes (top,bottom,left,right).
By default when you add a view, there are some prototyping constraints settled by default. That's why by default translatesAutoresizingMaskIntoConstraints is equal to true.
So if I take your code in your function playerView you are saying to the compiler :
Don't worry I compute the height for this view by myself
Then you disable your frame layout based and said to the compiler to handle this by putting anchor constraints.
RESULT => The compiler is lost obviously because the size that you compute (height, width) and your anchor constraints have conflicts.
So you have to decide by computing yourself your sizes and locations for your views OR use autolayout. Then on your error console you can list all the conflicted constraints and try to resolve them.
P.S : One advice when you want to fix left right anchor constraints, you should prefer trailing and leading anchors. Apple recommends it. And to go any furthere is the Apple documentation to learn about layout and autolayout : Apple doc

Complex Dynamic Collection Of UILabels Two Columns and Multiple Rows iOS

I've been struggling thinking how to setup this kind of layout in my tableViewCell. See photo:
More info:
Data is dynamic. There might be other days and each days might consists of multiple set of time.
Auto-layout is needed of course for dynamic/responsive height of my cell.
What I already did:
I did try doing this using IB.
I also tried it programmatically.
Both in IB and programmatically, I used UIStackView. But kinda hard to set it up.
I'm thinking to set it up using UIViews as containers, just like UIStackView but less complex.
I'm setting this up row by row. First is to line up the time vertically, and then the view or stackview of that will be paired horizontally with the Day. After that, do the same with the other days.
For formality of the question, here is a part of my code in my cell for setting up this layout, I suggest not taking an effort to read it, I believe I know what I am doing, and I think I just need another approach from you guys.
var job: Job! {
didSet {
_ = self.subviews.map {
if $0 is UIStackView {
$0.removeFromSuperview()
}
}
GPLog(classSender: self, log: "🎉Setting up stackview for JobShift")
// Add the main vertical stackView
let stackView_Vertical = UIStackView(frame: .zero)
stackView_Vertical.translatesAutoresizingMaskIntoConstraints = false
stackView_Vertical.alignment = .fill
stackView_Vertical.distribution = .fillProportionally
stackView_Vertical.axis = .vertical
stackView_Vertical.spacing = 16.0
self.addSubview(stackView_Vertical)
// Add constraints
stackView_Vertical.topAnchor.constraint(equalTo: self.topAnchor, constant: 15.0).isActive = true
stackView_Vertical.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -15.0).isActive = true
stackView_Vertical.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15.0).isActive = true
stackView_Vertical.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -15.0).isActive = true
if let dummyJson = self.readJson() {
if let shiftsJsonArray = dummyJson.array {
for shiftJson in shiftsJsonArray {
let newShift = DummyDataShift(json: shiftJson)
if let day = newShift.day,
let schedules = newShift.schedule {
let generatedStackView = self.generateDayScheduleStackView(day: day, schedules: schedules)
stackView_Vertical.addArrangedSubview(generatedStackView)
}
}
}
}
}
}
// MARK: - Functions
// Generate the full schedule stack view.
func generateDayScheduleStackView(day: String, schedules: [DummyDataSchedule]) -> UIStackView {
// label day (e.g. MONDAY)
let newLabel_Day = self.shiftLabel
newLabel_Day.translatesAutoresizingMaskIntoConstraints = false
newLabel_Day.text = day
newLabel_Day.backgroundColor = .red
newLabel_Day.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
// Prepare the vertical schedule stackView
let stackView_Schedule = UIStackView(frame: .zero)
stackView_Schedule.alignment = .fill
stackView_Schedule.distribution = .fillEqually
stackView_Schedule.axis = .vertical
// Add the schedules to the stackView vertically
for schedule in schedules {
let newLabel_Time = self.shiftLabel
newLabel_Time.text = "\(schedule.timeIn!) - \(schedule.timeOut!)"
newLabel_Time.backgroundColor = self.getRandomColor()
newLabel_Time.translatesAutoresizingMaskIntoConstraints = false
newLabel_Time.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
stackView_Schedule.addArrangedSubview(newLabel_Time)
}
// Prepare the horizontal dayScheduleStackView
let stackView_DaySchedule = UIStackView(frame: .zero)
stackView_DaySchedule.alignment = .fill
stackView_DaySchedule.distribution = .fillProportionally
stackView_DaySchedule.axis = .horizontal
// Add arranged subViews
stackView_DaySchedule.addArrangedSubview(newLabel_Day)
stackView_DaySchedule.addArrangedSubview(stackView_DaySchedule)
return stackView_DaySchedule
}
Problem is: Lots of warnings for broken constraints, I do know how to set up and fix constraints. But when I fix it, nothing is displaying. I feel like I'm wasting my time pushing and trying hard to continue this approach. So I thought that it would be me a lot if I ask for suggestions?
There is multiple ways of solving your problem. I will try to give you one approach to it.
1) Use a TableView, with each section cell containing the "day of the week" label plus a vertical StackView for the time labels (use equal spacing).
2) If you set your constraints properly, you can return UITableViewAutomaticDimension on sizeForRow: delegate.
override func tableView(_ tableView: UITableView, heightForRowAt
indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Then, on the cellForRowAt method, you can append labels to your vertical stack view with the times. The height of each cell will be correct as it will come from the constraints.
Actually, I think stack views a perfectly good way to go. They are about arranging things in rows and columns, which is precisely what you want to do. I had no difficulty arranging some labels in imitation of your specifications, using stack views alone, in Interface Builder:
The delightful thing about stack views is that once they are configured properly, they adapt as arranged subviews are added or removed. So they are dynamic in exactly the way you desire.

ScrollView not scrolling with subviews

I couldn't get my UIScrollView to scroll.
Here is my code:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame : CGRect ( x:0,y:0,width:UIScreen.main.bounds.width,height:UIScreen.main.bounds.height))
scrollview.delegate = self
view.addSubview(scrollView)
for i in 0...14 {
let numLabel = UILabel(frame : CGRect( x : 0 , y : 10+(i*40) , width : UIScreen.main.bounds.width - 20 : height : 40))
numlabel.text = "\(i)"
scrollView.addSubview(numLabel)
}
}
}
This is making the views appear but not scrolling.
Scroll view scrolls to its content size.
Whenever you add a subview to scroll view you should make sure that your scroll view's content size is enough to fit the new view.
In your case you are not taking care of that.
Ideally whenever you add a subview you should correspondingly adjust the hight of the scroll view content size.
In your case after you have added all of the labels to scroll view i.e. after for loop add following line
self.scrollView.contentSize = CGSize(width:self.scrollView.bounds.size.width,height:10+(15‌​*40))
or in the for loop after adding label you can do the following
self.scrollView.contentSize = CGSize(width:self.scrollView.bounds.size.width,height:10+((i+1)*40))
The second approach is better. Because if you add more labels to scroll view it will take care of that. Again, make it a rule of thumb, whenever adding a view to scroll view make sure that its content size is updated to fit all the subviews.
While you can manually set the contentSize, I would not advise doing that.
Instead, I'd use constraints for the subviews of the scroll view. The auto-layout engine will calculate the contentSize for you automatically. It will also take care of adjusting everything if the device rotates.
I'd also suggest using a stack view, you don't have to mess around with either manual frames for the labels nor with constraints between them.
So, you can do something like:
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
for i in 0 ... 140 {
let numLabel = UILabel()
numLabel.translatesAutoresizingMaskIntoConstraints = false
numLabel.text = "\(i)"
numLabel.font = .preferredFont(forTextStyle: .body)
stackView.addArrangedSubview(numLabel)
}
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
])
}
Note the use of UIFont.preferredFont(forTextStyle: .body) to enjoy Dynamic Text. This means that if the user has a larger font specified in their settings, this will automatically show the larger font in this app. But more importantly, we didn't have to calculate the size of the label for that font. Constraints and the stack view took care of both the frames of the labels as well as the scroll view's contentSize.
For the sake of completeness, it's worth noting that the alternative is to use a UITableView or UICollectionView. This is a scalable, memory efficient way of viewing data within a scroll view. It's beyond the scope of this question, but it's worth remembering as you consider creating large scroll views.

UIScrollView not scrolling at all when added programmatically in Swift?

So, I have looked through almost all of Stackoverflow's answers to this particular question and even looked through tutorials that supposedly teach you how to use a scroll view but It doesn't seem to apply for my project..
Here is what I know so far, in order for a Scroll View to properly work you first need to give it a content size. This determines the scrollable height etc.
I have provided some code to give you all a better idea of how I am adding said items into my scrollview. If there is something that I am doing wrong or if there is a better way to go about doing this please let me know, I am still fairly new to Swift and iOS development and in my mind it feels like I am doing it correctly.
The steps I am taking
Create items that I want to display (Input fields, Imageviews etc..)
Add said items to the view of the viewcontroller. (view.addsubview(etc..))
Create a scrollView and set its constraints to be same as the screen / view
Add our view with all the items in it into said scroll view
Relax and everything should work out perfect?????
Here is my code, I know it might be lengthy but I think it might be needed so that the scope of my question is understood
class JobRegistrationController: UIViewController {
// ... Omitted for clarity
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: UIScreen.main.bounds)
view.backgroundColor = .red
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//... Omitted for clarity
let scrollContentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Need so that view controller is not behind nav controller
self.edgesForExtendedLayout = []
view.addSubview(scrollView)
scrollView.addSubview(scrollContentView)
scrollContentView.addSubview(jobTypeField)
scrollContentView.addSubview(jobTypeDividerLine)
// x, y, width and height constraints
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
scrollContentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollContentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollContentView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
jobTypeField.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeField.topAnchor.constraint(equalTo: scrollContentView.topAnchor).isActive = true
jobTypeField.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor, constant: -8).isActive = true
jobTypeField.heightAnchor.constraint(equalToConstant: 30).isActive = true
// x, y, width and height constraints
jobTypeDividerLine.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeDividerLine.topAnchor.constraint(equalTo: jobTypeField.bottomAnchor).isActive = true
jobTypeDividerLine.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor).isActive = true
jobTypeDividerLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
Use this method in your class
override func viewDidLayoutSubviews()
{
scrollView.delegate = self
scrollView.contentSize = CGSize(width:self.view.frame.size.width, height: 1000) // set height according you
}
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
You should try to log the contentSize in your console after trying to access it. I am not sure if you are setting the correct contentSize here if the self.view.bounds has been calculated correctly when this gets called at that moment. Since it takes time for self.view frame and bounds to be calculated.
Try setting your contentSize after you have added the actual content to it based on the actual total content size.
EDIT:
Add a single UIView inside the scrollView, with the constraints set to top-bottom-leading-trailing, and add your subviews to it. Also, set the same constraints on the scrollView to the superView top-bottom-leading-trailing.
I believe the line of code below is the problem
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
You are setting your content view to the top of the view, when you should be setting it to the top of the scrollview.
I've just overcome a similar issue were I was setting the topAnchor of my first view to the safeAreaLayoutGuide.topAnchorof the scrollView. Everything laid out correctly but the constraint wouldn't show and therefore the entire content of the scrollView didn't move.
The problem is that you don't tell where the bottom of your content is. In other words you need some bottom constraints.
If you use...
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
...you need also to add a constraint to bind at least one view to the bottom of your UIScrollView like:
scrollContentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
... and also bind the last view in the scrollContentView to its bottomAnchor.
jobTypeDividerLine.bottomAnchor.constraint(equalTo: scrollContentView.bottomAnchor).isActive = true
This will sure fix your issue. Because this way the whole constraint sequence is linked from top to bottom.
Bottom line, the UIScrollView is not that smart that it determines its own bottom in every possible way. It is a kind of lazy. If you don't tell him enough it wouldn't simply scroll, while it is clear that your content disappears behind the bottom of your UIScrollView container.

Resources