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:
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.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I want to open a view controller over an existing view controller via button click without using storyboards. How do I do this? Here is what I mean:
Let's say we have three view controllers I can scroll between:
"zeroVC", "oneVC", and "twoVC"
When I press a button on "twoVC" I want to now scroll between:
"zeroVC", "oneVC", and "threeVC"
I tried looking all through stack overflow but they all use storyboards.
Let's assume we have four view controllers: RedViewController, GreenViewController, BlueViewController, and the one to contain them all, ContainerViewController.
Although you mentioned a scrolling view controller with three children within, we'll make it a two screen setup to keep it simple.
The following approach is scalable, so you would easily adopt it with an arbitrary number of view controllers.
Our RedViewController is 7 lines long:
class RedViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .red
self.view = view
}
}
Before we move on to GreenViewController and BlueViewController, we will define protocol SwapViewControllerDelegate:
protocol SwapViewControllerDelegate: AnyObject {
func swap()
}
GreenViewController and BlueViewController will have a delegate that conforms to this protocol, which will handle the swapping.
We will make ContainerViewController conform to this protocol.
Note that SwapViewControllerDelegate has the AnyObject in its inheritance list to make it a class-only protocol–we can thus make the delegate weak, to avoid memory retain cycle.
The following is GreenViewController:
class GreenViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .green
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
It has weak var delegate: SwapViewControllerDelegate? which will handle the swap when the button added in viewDidLoad is touched, triggering the swapButtonWasTouched method.
BlueViewController is implemented likewise:
class BlueViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .blue
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
The only difference is the view's backgroundColor and the button's titleColor.
Finally, we'll take a look at ContainerViewController.
ContainerViewController has four properties:
class ContainerViewController: UIViewController {
let redVC = RedViewController()
let greenVC = GreenViewController()
let blueVC = BlueViewController()
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
scrollView.isPagingEnabled = true
return scrollView
}()
...
}
scrollView is the view that will contain child view controllers, redVC, greenVC, and blueVC.
We will use autolayout, so don't forget to mark translatesAutoresizingMaskIntoConstraints as false.
Now, setup autolayout constraints of the scrollView:
class ContainerViewController: UIViewController {
...
private func setupScrollView() {
view.addSubview(scrollView)
let views = ["scrollView": scrollView]
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[scrollView]|",
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[scrollView]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }
}
...
}
I used VFL, but you can manually set autolayou constraints as we did for the button above.
Using autolayout, we don't have to set contentSize of the scrollView ourselves.
For more information about using autolayout with UIScrollView, see Technical Note TN2154: UIScrollView And Autolayout.
Now the most important setupChildViewControllers():
class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild($0) }
let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
$0.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][greenVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][blueVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }
[redVC, greenVC, blueVC].forEach { $0.didMove(toParent: self) }
greenVC.view.isHidden = true
greenVC.delegate = self
blueVC.delegate = self
}
...
}
We first add each of [redVC, greenVC, blueVC] as child view controllers of ContainerViewController.
Then add the view's of child view controllers to scrollView.
Set widthAnchor and heightAnchor of the child view controllers to be view.widthAnchor and view.heightAnchor, in order to make them fullscreen.
Moreover, this will also work when the screen rotates.
Using views dictionary, we use VFL to set autolayout constraints.
We will put greenVC.view on the right of redVC.view: H:|[redVC][greenVC]|, and similarly for the blueVC.view: H:|[redVC][blueVC]|.
To fix the vertical position of greenVC.view and blueVC.view, add .alignAllTop option to the constraints.
Then apply vertical layout for redVC.view, and set the height of the greenVC.view and blueVC.view: "V:|[redVC(==greenVC,==blueVC)]|.
The vertical position is set, as we used .alignAllTop while setting the horizontal constraints.
We should call didMove(toParent:) methods on the child view controllers after we add then as child view controllers.
(If you are wondering about what didMove(toParent:) and addChild(_:) methods do, apparently they do very little; see What does addChildViewController actually do? and didMoveToParentViewController and willMoveToParentViewController.)
Finally, hide greenVC.view, and set greenVC.delegate and blueVC.delegate to self.
Then of course, we need ContainerViewController to conform to SwapViewControllerDelegate:
extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}
That's it!
The entire project is uploaded here.
I recommend reading Implementing a Container View Controller, which is well-documented by Apple. (It is written in Objective-C, but it is actually straightforward to translate into Swift)
I want to set a UISwitch within a UINavigationBar. But when I try place my finger on the switch and drag it to "switch" on and off the view is not responding.
This is what i have.
https://github.com/rchampa/views-within-navigationItem
As already stated in the comments above your GitHub project does not contain any data. Nevertheless everything works as expected (and seems cleaner to me) if you set the custom UIBarButtonItem up programmatically:
override func viewDidLoad() {
super.viewDidLoad()
setupBarButtonItem()
}
private func setupBarButtonItem() {
let offLabel = UILabel()
offLabel.font = UIFont.boldSystemFont(ofSize: UIFont.smallSystemFontSize)
offLabel.text = "OFF"
let onLabel = UILabel()
onLabel.font = UIFont.boldSystemFont(ofSize: UIFont.smallSystemFontSize)
onLabel.text = "ON"
let toggle = UISwitch()
toggle.addTarget(self, action: #selector(toggleValueChanged(_:)), for: .valueChanged)
let stackView = UIStackView(arrangedSubviews: [offLabel, toggle, onLabel])
stackView.spacing = 8
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: stackView)
}
#objc func toggleValueChanged(_ toggle: UISwitch) {
print("new value: \(toggle.isOn)")
}
Update:
I made it work via storyboard too. In contrast to setting it up programmatically you have to embed the UIStackView into a regular UIView to be able to add it as a UIBarButtonItem in storyboard. Then I added top, leading, bottom and trailing constraints (each with a constant of 0) from the UIStackView to its superview. To get rid of the storyboard warnings and errors at design time (at runtime it works without any problems) you have to manually calculate and set the width for the outer view (which contains the UIStackView) that is needed to enclose all of it subviews (offLabel.width + spacing + toggle.width + spacing + onLabel.width).
I have a programmatically made view controller with subviews that are positioned using constraints. When I push this view controller into view using a navigation controller with the animation disabled...
let viewController = InventoryViewController()
navigationController?.pushViewController(viewController, animated: false)
...the view controller does not simply appear on screen, its subviews (the ones with constraints) expand outward and into view. Subviews that are not given constraints simply appear on screen as expected. So obviously, auto layout is animating itself into view. How do I disable this animation and just have the subviews appear on screen?
class InventoryViewController: UIViewController {
override func loadView() {
view = UIView()
view.frame = UIScreen.main.bounds
view.backgroundColor = UIColor.white
addButton()
}
func addButton() {
let button = UIButton()
button.backgroundColor = UIColor.black
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 36).isActive = true
button.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -32).isActive = true
button.heightAnchor.constraint(equalToConstant: 48).isActive = true
}
#objc func buttonAction() {
//
}
}
Pushing the view controller is what causes its view to be loaded. You could try to force this to load and layout before the push:
let viewController = InventoryViewController()
viewController.view.layoutIfNeeded()
navigationController?.pushViewController(viewController, animated: false)
loadView() is not a good place to configure your subviews, it should be used only to put the views in your view hierarchy, probably that is why you view is animating.
Try to set your subviews on viewDidLoad() method.
Please look the discussion section in the official documentation: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview
Call your method from viewDidLayoutSubviews after super.viewDidLayoutSubviews. And animation will not be seen. As the reason behind this is using Autolayout, frame of your views are set when the autolayout engine starts its calculation.
This method is called when the autolayout engine has finished to calculate your views' frames
use
UIView.setAnimationsEnabled(false)
You use layoutIfNeeded() on the view they are added to, to force layout/redraw immediately.
Do this in viewDidLoad or try right after you have set the constraints.
or try:
layoutSubviews()
setNeedsLayout()
layoutSubviews()
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.