Typing on search bar breaks layout - ios

I have a tableView:
tableView = UITableView()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
let constraints = [tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.heightAnchor.constraint(equalToConstant: view.frame.height * 0.6)]
NSLayoutConstraint.activate(constraints)
And my searchController looks like:
func setupSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search for a candy"
navigationItem.searchController = searchController
definesPresentationContext = true
}
When it loads for the first time everything is fine. But when I tap on the search bar to type there, my search bar moves to the top, navigation bar becomes smaller and it breaks my layout and white space appears between tableView and searchBar:
How can I improve my constraints to move up and down my tableView when the navigation bar sizes are changed?

You have to change your top constraint of the tableview.
Try replacing this 2 constraint
tableView.topAnchor.constraint(equalTo: searchController.searchBar.bottomAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor)
With this
tableView.topAnchor.constraint(equalTo: view.topAnchor)
The automatic content inset adjustment will do the rest.
Note also that in your table view constraints list you doesn't have a leading/horizontal position constraint. I suggest to add also this constraint
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor)

Related

How to disable drag space between two collections views in ios

I am using two collectionViews A and B one below the other. (I am using A to display headers and B containing the set of data below the header collection view A).
Now when I scroll the Collection view B to top and reach the end point I get a gap between A and B, which is not the expected output behaviour. What should I do to make A drag along with B without any space while scrolling.
TLDR - I've added a gif showing the issue.
func configureHeaderCollectionView() {
headerCollectionView.backgroundColor = UIColor.systemBackground
view.addSubview(headerCollectionView)
headerCollectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
headerCollectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
headerCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
headerCollectionView.heightAnchor.constraint(equalToConstant: 50).isActive = true
headerCollectionView.delegate = self
headerCollectionView.dataSource = self
headerCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "header")
}
func configureCollectionView() {
collectionView.backgroundColor = UIColor.systemBackground
view.addSubview(collectionView)
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: headerCollectionView.bottomAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
}
I am calling these two functions in viewDidLoad to setup the collection views.
I had the same problem but with a simpler GUI.
Try to disable scrolling of collectionView. This will not disable scrolling of cells, headers and footer.
collectionView.isScrollEnabled = false

Can a UISearchController be used in a child view controller?

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.

MSMessagesAppViewController messes up UITableViewController scrolling

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.

How to turn off adjusting large titles by UITableView in iOS 11?

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

Hide view when user scrolls down in tableview

I have a filterbar, which is a UIView as well as a tableView in my controller like so:
override func viewWillLayoutSubviews() {
sortSalonsByDistanceAndReload()
}
func setupViews() {
view.addSubview(filterBar)
view.addSubview(tableView)
filterBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
filterBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
filterBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
filterBarHeightLC = filterBar.heightAnchor.constraint(equalToConstant: 44)
filterBarHeightLC?.isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I want to make the filterBar dissapear when the user scrolls down on the tableView, and then reappear when the user scrolls up
You can use Search Controller. Take a look at How to implement UISearchController in UITableView - SWIFT
if it doesn't work out for you , you could add as a section or part of header view
The delegate of your tableView should implement the UIScrollViewDelegate method scrollViewDidScroll: and query the contentOffset.y property on the scrollView (your tableView). Then it depends on when you want the bar to appear: only on the top, or also whenever the user scroll up inside the tableView. You could sync the contentOffset.y difference with the filterBars top constraint's constant (ranging from -44 to 0). It might be better to inset your tableView and update that inset as well (from 0 to 44).
If your filterBar could be implemented with a navigation bar from a UINavigationController you might just do navigationController?.hidesBarsOnSwipe = true, but I don't know your view controller setup.

Resources