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.
Related
I have added a tabBarController and hooked it up to viewControllers and given the tabBarController its own class. It works, but I would like to customize it by changing the constraints so that it's not right at the bottom. From what I can see there's no way to add constraints in auto layout as it's all grayed out. I gave a shot at adding it programmatically, but nothing happens.
final class TabBarViewController: UITabBarController {
#IBOutlet var customTabBar: UITabBar!
override func viewDidLoad() {
self.selectedIndex = 2
let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0)]
UITabBarItem.appearance().setTitleTextAttributes(fontAttributes, for: .normal)
//This doesn't work
customTabBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
//Neither does this
self.tabBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
//Or this
if let tabC = self.tabBarController {
tabC.tabBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
}
So how do I do this? I want the tabBar to be about 50p from the bottom.
You can create a container view controller to hold you UITabBarController.
Then, when you set the constraints on your container view, you can add 'padding' to the bottom, to have it go upwards. Below are two screenshots explaining the layout.
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)
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
i would like to know how can i adjust my UIButton to be stationary at the bottom so lets say even if there are a lot of tableview cells. The button will still stay put in the bottom.
Ok, to get a FIXED button on bottom on as you required follow the below steps:
Add a UIViewController on your Storyboard
Add a UITableView on UIViewController
Add a UIView at bottom of UIViewController with Fixed Height
Add Constraints to UIView for bottom, left, right to its super view & Make UIView.height Constraints a fix value
Add Constraints to UITableView for top, left, right to its superview & bottom with UIView
Add a UIButton on UIView with Horizontally & Vertically centre to its superview
for your reference demo project zip also added with these steps
Actually there are 3 ways to achieve that in my knowledge.
1 -> By Using UIViewController instead of UITableViewController
In UIViewController you can add UITableView , then add UIButton at the bottom of the UIViewController as per your requirement.
2 -> Add Button on the UINavigationController View
You can add a button on UINavigationController's view (if you are using UINavigationController) programmatically.
3 -> Add Button on the Application Window
You can add a button on UIWindow programmatically.
For 2nd & 3rd point
These cases are applicable when you want to use only UITableViewController. For this you need to create a button programmatically in your TableViewController.
let button : UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Redeem", for: .normal)
button.backgroundColor = .green
return button
}()
Then add the button either on UIWindow or on UINavigationController's view , like this:
If you want to add button UIWindow
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let window = UIApplication.shared.keyWindow
{
window.addSubview(button)
button.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
let bottomSpaceConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[v0(40)]-20-|", options: .init(rawValue: 0), metrics: nil, views: ["v0" : button])
NSLayoutConstraint.activate(bottomSpaceConstraints)
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
button.removeFromSuperview()
}
and if you want to add it in UINavigationController.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let navigationView = self.navigationController?.view
{
navigationView.addSubview(button)
button.centerXAnchor.constraint(equalTo: navigationView.centerXAnchor).isActive = true
let bottomSpaceConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[v0(40)]-20-|", options: .init(rawValue: 0), metrics: nil, views: ["v0" : button])
NSLayoutConstraint.activate(bottomSpaceConstraints)
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
}
Remember
In both cases 2nd & 3rd, You should remove that button in viewWillDisappear method, cause you don't want that button appear on every screen.
In All cases result would be something like this:
I've noticed some strange behavior of UITablesViews inside a ViewController when said ViewController is embedded within a UINavigationController. The following is the code of a simple prototype for selecting different UITableViews to be shown in a scene excluding the methods for the TableView and selecting which view to show.
class ChooseTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableViewA: UITableView = UITableView()
var tableViewB: UITableView = UITableView()
var colors: [String] = ["red", "blue", "green"]
var shapes: [String] = ["triangle", "circle", "square"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableViewA.frame = CGRectMake(0, 50, 320, 200)
tableViewA.delegate = self
tableViewA.dataSource = self
tableViewA.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellTableViewA")
tableViewB.frame = CGRectMake(0, 50, 320, 200)
tableViewB.delegate = self
tableViewB.dataSource = self
tableViewB.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellTableViewB")
self.view.addSubview(tableViewB)
self.view.addSubview(tableViewA)
tableViewB.hidden = true
let segmentSelectorLabels = ["one", "two"]
let segmentSelector = UISegmentedControl(items: segmentSelectorLabels)
segmentSelector.frame = CGRectMake(self.view.frame.width/2 - 50, self.view.frame.height - 100, 100, 40)
self.view.addSubview(segmentSelector)
segmentSelector.selectedSegmentIndex = 0
segmentSelector.addTarget(self, action: "chooseTable:", forControlEvents: .ValueChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
...
}
and here are the results of when ChooseTableViewController is not embedded in a UINavigationController and when it is
Simulators results
In another project the behavior can be fixed by simply adding
let emptyView:UIView = UIView()
and
self.view.addSubiew(emptyView)
with emptyView being the first added subview. This is a hackney solution to having the UITableViews placed in their proper position. Any insight to this behavior is appreciated.
uncheck "Adjust Scroll View Insets" for the viewcontroller in the Attributes inspector or do it in code:
self.automaticallyAdjustsScrollViewInsets = NO; (Obj-C)
self.automaticallyAdjustsScrollViewInsets = false (Swift)
some more explanation:
if this property is set to YES / true the viewcontroller - as the name says - automatically adjusts the insets for the first scrollview in its view hierarchy. this can be helpful if your scrollview / tableview / textview / webview takes up the whole screen and parts of it are normally hidden by the statusbar / navigationbar / tabbar or toolbar. then those insets make your content appear below the top bars / above the bottom bars. this though only happens when the scrollview / ... is the TOP MOST subview in the view hierarchy (= the subview at index 0). to make things clearer i uploaded four examples:
property set to YES / true and two textviews in the view hierarchy (at index 0 and 1): as you can see the insets are only set for the first scrollview
property set to NO / false and two textviews in the view hierarchy (at index 0 and 1): as you can see no insets are set at all
property set to YES / true, a button (subview at index 0) and two textviews (at index 1 and 2): as you can see no insets are set at all although the property is YES / true. that is because no scrollview is the TOP MOST subview in the view hierarchy.
property set to YES / true an a textview in the view hierarchy at index 0 taking up the whole screen: as you can see although the textview takes up the whole screen (starts at 0,0) the text is not hidden by the status- / navigationbar because the viewcontroller automatically adjusted the textview's (scrollview's) inset.
i hope i could help making things a bit clearer. :)
Set table view's frame origin y-positions to 0. Find "Extended Edges" settings for this view controller in storyboard and turn off "Under Top Bars".