How to show UITableView below a UITextField using swift programmatically? - ios

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)

Related

UITableView setting row Height not having effect

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
}

How to put a UITextView at the bottom of the TableViewController?

I want to know what is the correct way to put a UITextView at the bottom of the TableViewController, I don't want it to put in a cell because I already know how to do that, and that is not what I am trying to do, I want to put a UITextView at the bottom of the tableViewController
class ExampleTableViewController: UITableViewController, UITextViewDelegate {
let firstView = UITextView()
first.translatesAutoresizingMaskIntoConstraints = false
func setUpTextView() {
firstView.delegate = self
view.addSubview(firstView)
NSLayoutConstraint.activate([firstView.topAnchor.constraint(equalTo: view.topAnchor),
firstView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
firstView.leftAnchor.constraint(equalTo: view.leftAnchor),
firstView.rightAnchor.constraint(equalTo: view.rightAnchor)])
} }
Replace
firstView.topAnchor.constraint(equalTo: view.topAnchor)
with
firstView.heightAnchor.constraint(equalToConstant:50)
since the view of the tablecontroller is the tableView then any view added will scroll with it so it's better to create
class ExampleTableViewController: UIViewController, UITextViewDelegate {
a usual vc with tableView && textView with constraints set properly
Edit: so either implement scrollViewDidScroll and change the bottom constraint as the table with it's tableView.contentOffset.y
OR
set the view as a footer like
self.tableView.footerView = firstView

Dynamic corner radius for a UITextView

I am trying to make my uitextview's corner radius dynamic depending on the number of lines displayed. The text is retrieved from my backend, so the number of lines will vary...
I've tried setting the corner radius in viewDidLoad after the text view is created by using its frame but for some reason that doesn't work. I'm assuming its returning 0 for some reason.
Any help is greatly appreciated.
(I'm cutting out a lot of code just to keep it simple here. Everything is added to the subview properly. Everything is added programmatically - not using the storyboard at all here. The text views display as expected besides the corner radius)
inside viewDidLoad:
questionOneTextBox.layer.cornerRadius = self.questionOneTextBox.frame.height * 0.5
questionTwoTextBox.layer.cornerRadius = self.questionTwoTextBox.frame.height * 0.5
If you are creating the text view programmatically with constraints, then you need to call self.questionOneTextBox.layoutIfNeeded() and self.questionTwoTextBox.layoutIfNeeded(). This will initialise the bounds.
class ViewController: UIViewController {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the Text View
self.textView = UITextView()
self.textView.text = "This text will appear in the text view."
self.textView.backgroundColor = .lightGray
self.view.addSubview(self.textView)
// Set the constraints
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.textView.widthAnchor.constraint(equalToConstant: 280).isActive = true
self.textView.heightAnchor.constraint(equalToConstant: 60).isActive = true
self.textView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 60).isActive = true
self.textView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// This will initialise the bounds
self.textView.layoutIfNeeded()
// Now the this should work
self.textView.layer.cornerRadius = self.textView.frame.height * 0.5
}
}
This what this code looks like.
Views are not laid out (sized) when viewDidLoad is called. Move your corner radius sizing to viewDidLayoutSubviews or viewWillAppear, at which time the sizing has occurred.
Did you set clipsToBounds to true? This might be causing your problem. I hope this helped

Sizing of table view cell and table view header view

I have a table view which displays table view cells and a header view (search bar). I want the width of the table view cells to be 0.9 of the device screen size, and the header view to have the same size. However, this is not possible because cells and the header view are all contained in a table view.
When I use layout anchor constraints everything gets resized (including the header view).
self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
// here's the 0.9 multiplier
self.tableView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.9).isActive = true
self.tableView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
How can I make sure that the table view header has the same width of the view controller, while keeping the table view width to be 0.9 of the main view?
It is best not to try to adjust the constraints of a TableViewCell or its contentView. However, you can just set both those views to have a background color of .clear and then add subviews to the contentView with the constraints you want.
class CustomCell: UITableViewCell {
let smallerView = UIView()
override init(frame: CGRect) {
super.init(frame)
smallerView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(smallerView)
smallerView.widthAnchor.contraint(equalTo: contentView.widthAnchor, multiplier: 0.9).isActive = true
smallerView.heightAnchor.contraint(equalTo: contentView.heightAnchor).isActive = true
}
}
Your custom cell "starts with" a ContentView, which uses uses the table view's insets to set its width...
So, if you have a bunch of elements / objects that you are adding to the content view...
Your best bet is to probably first add a standard UIView as a "container view"... (just my naming of it)...
Then, set the constraints on that "containing view" to be 0.9 of the width of the contentView...
All of the elements you add to that "containing view" will be constrained relative to it and thus will all stay inside the 0.9-width overall...

Add button on top of UITableViewController (Swift)

I am trying to add a button ontop of a uitableview controller table view. The view controller has a navigation controller and static cells, which is why it is a uitableviewcontroller and not a uiviewcontroller. Now I am trying to add a button at the bottom of the screen that is attached to the navigation controller so that it doesn't scroll with the table view.
I am trying to make something similar to what is below. It has a navigation controller for the top bar, a table view with static cells and then a button, but how did they do the button?
Image: http://postimg.org/image/ilsmqqrip/
Thanks!
UPDATE: How can I use a uiviewcontroller with a tableview with static cells using Swift?
I find Container Views very useful in this scenario! A clean solution and very easy to implement.
Just create a normal UIViewController, add your button and a ContainerView as subviews of this UIViewController (the middle one in the image below). Finally create Embed Segue from ContainerView to your UITableViewController (the one on the right).
This way you can use static cell prototypes, not being limited only to UITableView at the same time.
Result:
there is a better solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
//create a button or any UIView and add to subview
let button=UIButton.init(type: .system)
button.setTitle("NEXT", for: .normal)
button.frame.size = CGSize(width: 100, height: 50)
self.view.addSubview(button)
//set constrains
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}
I did something similar with UITableViewController and a static datasource. I added the button in the footerview of my tableview.
To make it align to the bottom of the screen i needed this code in my viewcontroller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Make footerview so it fill up size of the screen
// The button is aligned to bottom of the footerview
// using autolayout constraints
self.tableView.tableFooterView = nil
self.footerView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.tableView.frame.size.height - self.tableView.contentSize.height - self.footerView.frame.size.height)
self.tableView.tableFooterView = self.footerView
}
In short, I resize the footerview to take up all the remaining space after the contentsize of the table view is removed. Since the button is aligned to the bottom of the footerView with autolayout, it will stay in the bottom of the screen.
The Storyboard:
Here is the result:
The UITableViewController will take up the whole space, so you won't be able to add the button. Refactor your UITableViewController based code into UIViewController with UITableView manually added. This way you will be able to set the size of your table view and put the button to the bottom.
Unfortunately UITableViewController has a tableView as its top level view. Of course if you look in the view debugger you can see that the tableview is not the root view. Therefore you can add the buttons to the tableView's window programatically. If you have to do it after the fact, this is probably the easiest way to add a top level element over a UITableViewController. Otherwise if you are doing it in the initial design, you can use container view for your buttons and a UITableViewController for the TableView. The downside of this approach is you end up with two view controllers, one for the container and one for the table and its often necessary to pass information back and for between them. If you are using swift you can simplify this by nesting the tableViewcontroller inside the container view controller class.
If you want to add a button to the window, you can do this lazily once you are sure that the view has a window. Note that the buttons belong to the window and not to the view controller, so its your responsibility to remove them when the view controller disappears.
private weak var button: UIButton!
...
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
guard self.button == nil, let window = tableView.window else {
return
}
let button = UIButton(frame: CGRect(x:0, y:40, width: 200, height: 20))
button.setTitle("This is a red button", for: .normal)
button.backgroundColor = UIColor.red
window.addSubview(button)
self.button = button
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
button?.removeFromSuperview()
}
Step 1 :-
Drag and drop one uiview to UITable View Controller (Static)
Automatically it sticks to the bottom.
If you need to, you can also add two buttons inside UIView... It depends on your requirements.
Step 2 :-
Connect the outlet for uiview (outletView)
Step 3 :-
Add this below code in View Will Appear.
override func viewWillAppear(_ animated: Bool) {
outletViewBottom.backgroundColor = .red
tableView.addSubview(outletViewBottom)
// set position
outletView.translatesAutoresizingMaskIntoConstraints = false
outletView.leftAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.leftAnchor).isActive = true
outletView.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor).isActive = true
outletView.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor).isActive = true
outletView.widthAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.widthAnchor).isActive = true
outletView.heightAnchor.constraint(equalToConstant: 50).isActive = true // specify the height of the view
}
Step 4 :-
Now run the code... Happy coding.
all you need to do is to add your Top view whichever it is to the navigationController.view like so:
self.navigationController?.view.addSubview(YOUR_TOP_VIEW)
so if you need a sticky button/view etc... on top of TableViewController which does not scroll with tableView, use this approach.
Here is a UIViewController, with a UITableView added as a subview. At the top right, you can see a dropdown that says Content: Dynamic Prototypes. Change it to Static Cells.

Resources