I am building a project without storyboard. Everything is working fine but I can't seem to figure why I can't add tableView programmatically. I have tried the same code for adding tableView in another empty project and its working fine but inside my project the tableview is not showing up. My view hierarchy is like below.
I have a BaseClass like this:
class BaseController: UIViewController{
override func viewDidLoad() {
setupViews()
}
func setupViews(){
}
}
Then I have firstViewController class inherited from Base Class:
class firstViewController: BaseController
Inside my firstController, I am declaring and initializing my tableView:
var tableView:UITableView = {
let tbl = UITableView()
tbl.translatesAutoresizingMaskIntoConstraints = false
tbl.backgroundColor = .blue
return tbl
}()
Then I am overriding setupView() inside firstViewController here like below:
override setupView() {
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
])
}
It should show empty tableView cells but it is not showing up. I guess there is something to do with the base and derive class thing but I can't figure out the exact problem.
You need to set trailing constraint for tableview correctly.
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
You were setting view.leading to tableview.trailing that makes your tableview invisible from current view.
Related
I'm attempting to recreate a UI similar to that of the one found in the Maps app—namely my focus is on the bottom drawer. I've created a container view controller DrawerViewController that is added to the root view controller ViewController. The DrawerViewController handles the positioning and interaction with the drawer itself, which then receives a child view controller as its content—in this case, by default, MapSearchViewController. I'm trying to add a UISearchController to the MapSearchController, which simply contains the UISearchBar from the search controller and a UITableView. I'm having the issue that when the search bar is tapped, both the search bar and table view are disappearing, and from the UI inspector, I can see a UITransitionView has been added.
After some extensive research, I was able to find quite a few SO answers where although the situation wasn't the exact same, the general trend among them was that definesPresentationContext must be true on the view controller containing the search controller. I added this, but alas, no dice. This did however, lead me to a temporary solution: in the DrawerViewController, I had been adding the MapSearchViewController as a child by doing:
let mapSearchViewController = MapSearchViewController()
contentView.addSubview(mapSearchViewController.view)
addChild(mapSearchViewController)
mapSearchViewController.didMove(toParent: self)
I found that by removing the addChild(_:) call fixes the issue of the search bar and table view disappearing when the search bar is tapped, but since the view controller hasn't been added as a child, all of the logic in the MapSearchViewController doesn't get executed. I assume its an issue with the view controller hierarchy as such or how I'm trying to add the search controller to the MapSearchController, but I have no idea what to even begin to look for to fix this.
import UIKit
class MapSearchViewController: UIViewController {
weak var delegate: MapSearchViewControllerDelegate?
var searchController: UISearchController!
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = false
let searchBar = searchController.searchBar
searchBar.searchBarStyle = .minimal
searchBar.placeholder = "Search for a building or place"
view.addSubview(searchBar)
searchBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: view.topAnchor),
searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 2.0),
searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -2.0),
searchBar.heightAnchor.constraint(equalToConstant: 56.0)
])
tableView = UITableView(frame: .zero)
tableView.dataSource = self
tableView.delegate = self
tableView.backgroundColor = .clear
tableView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor
tableView.layer.borderWidth = 1.0 / 3.0
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
// Both these extensions just have protocol stubs as of now, but I'm including them to show they exist.
extension MapSearchViewController: UISearchResultsUpdating {
...
}
extension MapSearchViewController: UITableViewDataSource {
...
}
I've foregone including the source for the DrawerViewController here, as I believe the only relevant part is the section included above where the MapSearchViewController is created and added. I haven't changed any properties of the DrawerViewController itself, only properties of the drawer's view. However, if anyone thinks they might know the issue and need to see the source, please leave me a comment and I can edit the question to include it.
I am trying to get my UITableView to be positioned within the safe area but it doesn't seem to be working and I do not know why. I trying to do this programatically.
class MenuTableViewController: UITableViewController{
var margin: UILayoutGuide!
var tableDataSource: [userFolderObject]!
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup(){
margin = view.layoutMarginsGuide
tableDataSource = MockData.UITableDateSource
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
/* I have also tried the below code
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
*/
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.backgroundColor = UIColor.lightGray
if section == 0{
label.text = "Search PubMed"
}else{
label.text = "My Folders"
}
return label
}
}
First, remove all of your setup code that attempts to mess with the margins of the table view. That is all done for you by default in a UITableViewController.
Since your issue is only with the layout of your custom section header views, you need to fix how you have implemented those views.
Like cells, you should use reusable header/footer views and your header/footer view should extend UITableViewHeaderFooterView. This will ensure proper margins by defaults and it already provides a standard textLabel you can set. No need to create your own UILabel.
As shown in the documentation for UITableViewHeaderFooterView you should register a class. Then in viewForHeader you should dequeque the header view and then set its textLabel as needed.
If you don't actually need anything but a plain old section label, then don't implement viewForHeader. Instead, implement titleForHeader. Much simpler.
When my MSMessagesAppViewController changes the presentationStyle from compact to expanded and back to compact, my UITableView is messed up regarding its scrolling.
I am using AutoLayout to setup a UITableView inside of a View called contentView.
// Inside MSMessagesAppViewController
func createTableView() {
let tableViewController = MyTableViewController()
self.addChildViewController(tableViewController)
tableViewController.tableView.backgroundColor = UIColor.clear
tableViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(tableViewController.tableView)
NSLayoutConstraint.activate([
tableViewController.tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
tableViewController.tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
tableViewController.tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
tableViewController.tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
After expanding and collapsing the MSMessagesAppViewController, I can suddenly scroll past my last cell in the tableView...
Everything is working perfectly fine in the initial state. I noticed, that the scrollbar is visible on startup, but is not present after the size change...
This is how I setup my tableView in the controller:
// Inside MyTableViewController
func setupTableView() {
tableView.dataSource = self
tableView.register(QuickStandardTableViewCell.self, forCellReuseIdentifier: "standardCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 64
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .clear
tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
tableView.estimatedSectionHeaderHeight = 8.0
self.extendedLayoutIncludesOpaqueBars = false
}
What I checked:
The contentView frame changes correctly
The tableView frame changes correctly
The contentInset does not change
The contentSize does not change
Does anybody know what I am missing?
Thanks in advance!
Try using a UIViewController with a UITableView inside instead of using UITableViewController.
I had the same problem with an UICollectionViewController and switching to UIViewController solved this problem.
There's this large titles feature in iOS 11 that shows large title when the UITableViewController's table is scrolled to top, and gets collapsed to standard small title when the user scrolls the table away from top. This is standard behavior. I need the navigation controller to behave a bit differently - I need to always show the large title. How to achieve this?
Following code does not help, it still collapses when scrolled.
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
I've achieved it unintentionally when embedded UITableViewController inside UIViewController.
I'm not sure whether it is an Apple's bug or intended behavior.
So stack is as simple as UINavigationController -> UIViewController(used as container) -> UITableViewController
Here is sample of view controller with embedded UITableViewController fullscreen
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var vc = UITableViewController(style: .plain)
var array: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
vc.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(vc.view)
view.addConstraint(view.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor))
view.addConstraint(view.rightAnchor.constraint(equalTo: vc.view.rightAnchor))
view.addConstraint(view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: vc.view.topAnchor))
view.addConstraint(view.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor))
vc.tableView.delegate = self
vc.tableView.dataSource = self
array = "0123456789".characters.map(String.init)
vc.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "identifier")
title = "Title"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
cell.textLabel?.text = array[indexPath.row]
return cell
}
}
Here is the result
Hope it helps.
P.S. Surprisingly, my current problem is that I don't know how to get collapsing behavior with such architecture :)
What I did was to add another view between navigationBar and TableView with a height of 1.
let tableViewSeperator: UIView = {
let view = UIView()
// remove the color, so it wont be visible.
view.backgroundColor = UIColor.systemBlue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
One thing which is important is add this seperator view as a subview of your viewcontroller's view before tableView, otherwise it won't work
view.addSubview(tableViewSeperator)
view.addSubview(tableView)
or if you want to save one line of code, you can also do it like this.
[tableViewSeperator, tableView].forEach({view.addSubview($0)})
Then set its constraints like this.
tableViewSeperator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
tableViewSeperator.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
tableViewSeperator.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
tableViewSeperator.heightAnchor.constraint(equalToConstant: 1).isActive = true
The last thing is change the tableView TopAnchor to be the BottomAnchor of sperator View.
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
tableView.topAnchor.constraint(equalTo: tableViewSeperator.bottomAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: createItemBtn.topAnchor, constant: 0).isActive = true
Now when you scroll the the NavigationBar will stay as Large.
You need to add UIView(it's can be width=0, height=0) before add UITableView.
example
Then this code will work
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
I add a UILabel (amountLabel) in UIViewController in storyboard editor. And then in swift file, viewDidLoad, I programatically create a UITextField (paymentTextField) and try to add a constraint between amountLabel and paymentTextField. Here is my code in viewDidload:
let paymentTextField = UITextField()
paymentTextField.translatesAutoresizingMaskIntoConstraints = false
paymentTextField.frame = CGRectMake(15, 100, CGRectGetWidth(self.view.frame) - 30, 44)
self.view.addSubview(paymentTextField)
let bottonConstraint = NSLayoutConstraint(item: paymentTextField, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.amountLabel , attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 30)
bottonConstraint.identifier = "paymentTextFieldSpacing"
NSLayoutConstraint.activateConstraints([bottonConstraint])
But I get an error:
"Terminating app due to uncaught exception 'NSGenericException',
reason: 'Unable to activate constraint with items > and > because they have no common ancestor.
Does the constraint reference items in different view hierarchies?
That's illegal."
Does anyone know what is wrong? amountLabel is directly dragged to the view in storyboard and "paymentTextField" is added programmatically to the same view. Why have they no common ancestor?
I ran into the same problem that you described earlier. In order to make the programmatic subview, (in your case the paymentTextField) you have to add this to the subview first and then apply your constraints.
By adding the subview to view first, this ensures both views have the same parent.
Checklist for this ISSUE:
Check whether you added the programmatically created view to its parent before activating constraints
Check whether you write constraints activation code inside viewDidLoad() / viewWillAppear(). You should write constraints activation code in updateViewConstraints or viewWillLayoutSubviews. ( suggested by vmeyer )
Check whether you turn off translatesAutoresizingMaskIntoConstraints.
The error states that "because they have no common ancestor", which means that they don't share the same parent. In order to relate constraint between two items, they have to have a child-parent relationship or a sibling one.
In your case just make sure they have the same parent view before adding the constraint programmatically.
Make sure you added the paymentTextField to your view:
paymentTextField.translatesAutoResizingMaskIntoConstraints = false
view.addSubview(paymentTextField)
...
Add your constraints, for example paymentTextField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
let nameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
if you forgot setting this label.translatesAutoresizingMaskIntoConstraints = false
or if you add subview after constraints
this problem arises
SWIFT 4 & 5
final class VoiceSearchViewController: UIViewController {
// MARK: - Properties
var backgroundView: UIView = {
let view = UIView()
view.layer.cornerRadius = 20
view.backgroundColor = .red
return view
}()
// MARK: - Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
initialSetup()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
dismiss(animated: true, completion: nil)
}
// MARK: - Private Methods
private func initialSetup() {
view.backgroundColor = .clear
view.addSubview(backgroundView)
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
backgroundView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
}