I'm trying to make a search application using MusicBrainz API, where the API will return JSON data that matches the search term typed in by the user.
This is what I have so far of my UI:
import UIKit
import WebKit
class ArtistListViewController: UIViewController{
let tableView = UITableView()
var safeArea: UILayoutGuide!
var artists: [Artists]?
let textField = UITextField()
var searchTerm = "Search"
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
safeArea = view.layoutMarginsGuide
searchBar()
setUpTable()
setUpNavigation()
}
func searchBar(){
view.addSubview(textField)
textField.placeholder = "Search"
textField.frame = CGRect(x: 10,y: 200,width: 300.0,height: 30.0)
textField.borderStyle = UITextField.BorderStyle.line
textField.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
textField.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpTable(){
view.addSubview(tableView)
ArtistSearchModelData().loadArtists(searchTerm: "Adele"){ [weak self] (artists) in
self?.artists = artists
DispatchQueue.main.async{
self?.tableView.reloadData()
}
}
//populate with data
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
//turn off autoresizing
tableView.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpNavigation(){
self.navigationItem.title = "Artists"
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.orange,
NSAttributedString.Key.font: UIFont(name: "Arial-BoldMT", size: 30)
]
}
}
This is what my UI looks like:
As you can see my search bar is completely missing and I have no idea how to render it.
I tried using a UIStackView but got the same results.
I've tried searching the internet and found similar solutions but couldn't get any of them to work.
Adding both textField and tableView to a custom subview renders nothing too, maybe because they're functions? Am I just going about this the wrong way?
Any help is appreciated!
You constrain your text field to the top of the view:
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
then, you constrain your table view to the top of the view:
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
So your table view is covering your text field.
You could do this:
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
To constrain the Top of the table view to the Bottom of the text field.
As a side note, you should constrain to the view's Safe Area ... not to the view itself:
textField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
Edit
Here is your class with the above modifications (note that I commented-out the stuff I don't have access to, such as your Artist specific code):
class ArtistListViewController: UIViewController{
let tableView = UITableView()
var safeArea: UILayoutGuide!
//var artists: [Artists]?
let textField = UITextField()
var searchTerm = "Search"
//var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
safeArea = view.safeAreaLayoutGuide
searchBar()
setUpTable()
setUpNavigation()
}
func searchBar(){
view.addSubview(textField)
textField.placeholder = "Search"
// not needed
//textField.frame = CGRect(x: 10,y: 200,width: 300.0,height: 30.0)
textField.borderStyle = UITextField.BorderStyle.line
textField.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
// constrain Top to safeArea Top
textField.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: safeArea.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: safeArea.rightAnchor).isActive = true
// don't constrain the bottom
//textField.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpTable(){
view.addSubview(tableView)
// ArtistSearchModelData().loadArtists(searchTerm: "Adele"){ [weak self] (artists) in
// self?.artists = artists
//
// DispatchQueue.main.async{
// self?.tableView.reloadData()
// }
// }
//
// //populate with data
// tableView.delegate = self
// tableView.dataSource = self
// tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
//turn off autoresizing
tableView.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
// constrain Top to textField Bottom
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: safeArea.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: safeArea.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}
func setUpNavigation(){
self.navigationItem.title = "Artists"
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.orange,
NSAttributedString.Key.font: UIFont(name: "Arial-BoldMT", size: 30)
]
}
}
If you run that code as-is, you should get your Search textField above the (empty) tableView.
If you then un-comment your Artist-specific code, it should work properly.
Update the constraints and it will work:
//Layout Configs
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
textField.heightAnchor.constraint(equalToConstant: 30).isActive = true
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
Couldn't exactly find out why you're using UIViewController instead of UITableViewController so here's a solution for the second one...
For those kind of tasks, instead of embedding a UITextField, there's a simpler, ready for use solution called UISearchController-
Use a search controller to provide a standard search experience of the
contents of another view controller. When the user interacts with a
UISearchBar, the search controller coordinates with a search results
controller to display the search results.
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating, UISearchControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
createSearchController()
}
// MARK: - Table view data source
let items = ["item1", "item2", "item3", "item4"]
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
cell.detailTextLabel?.text = items[indexPath.row] + " detail"
return cell
}
// MARK: - Search controller
private let searchController = UISearchController(searchResultsController: nil)
func createSearchController(){
searchController.searchResultsUpdater = self
searchController.delegate = self
tableView.tableHeaderView = searchController.searchBar
}
func updateSearchResults(for searchController: UISearchController) {
// Do something with it
let searchedText = searchController.searchBar.text
}
}
That's how it looks like in the end.
when use call two methods searchBar() setUpTable() - your tableView will be over searchBar. I mean the order that you chose to call this methods
any way when you call it like you did some cell at the top will hide under searchBar.
you can make this constraints
textField.translatesAutoresizingMaskIntoConstraints = false
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
or you can add your searchBar into navigationBar
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.searchController?.searchBar.delegate = self
navigationItem.searchController?.obscuresBackgroundDuringPresentation = false
navigationItem.searchController?.hidesNavigationBarDuringPresentation = false
navigationItem.searchController?.searchBar.placeholder = "Enter text here..."
Related
App crash when add some views (i. e. UIView, UITextView, UIImageView, ...) to main view
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
view.addSubview(textview)
}
}
How to create views by programmatically?
Edit: I forgot to add the view before setting constraints. So you need to set constraints after adding subviews.
You need to add subview first view.addSubview(textview) before adding constraints and set height and width also for textview as you can see updated code:
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textview.widthAnchor.constraint(equalToConstant: 150).isActive = true
textview.heightAnchor.constraint(equalToConstant: 80).isActive = true
}
}
First you need to add view.addSubview(textview) then add constraints.
import UIKit
class testViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// you need to specify height and width constraints as well otherwise UITextView will not appear
textview.widthAnchor.constraint(equalToConstant: 100).isActive = true
textview.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
}
You must set constraints after view.addSubview
like this :
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.heightAnchor.constraint(equalToConstant: 100).isActive = true
textview.widthAnchor.constraint(equalToConstant: 200).isActive = true
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
I've recently started learning swift and iOS app development. I've been doing php backend and low level iOS/macOS programming till now and working with UI is a little hard for me, so please tolerate my stupidity.
If I understand this correctly, stackviews automatically space and contain its subviews in its frame. All the math and layout is done automatically by it. I have a horizontal stackview inside a custom UITableViewCell. The UIStackView is within a UIScrollView because I want the content to be scroll-able. I've set the anchors programmatically (I just can't figure out how to use the storyboard thingies). This is what the cells look like
When I load the view, the stackview doesn't scroll. But it does scroll if I select the cell at least once. The contentSize of the scrollview is set inside the layoutsubviews method of my custom cell.
My Custom Cell
class TableViewCell: UITableViewCell
{
let stackViewLabelContainer = UIStackView()
let scrollViewContainer = UIScrollView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .black
stackViewLabelContainer.axis = .horizontal
stackViewLabelContainer.distribution = .equalSpacing
stackViewLabelContainer.alignment = .leading
stackViewLabelContainer.spacing = 5
for _ in 1...10
{
let labelView = UILabel();
labelView.backgroundColor = tintColor
labelView.textColor = .white
labelView.text = "ABCD 123"
stackViewLabelContainer.addArrangedSubview(labelView)
}
scrollViewContainer.addSubview(stackViewLabelContainer)
stackViewLabelContainer.translatesAutoresizingMaskIntoConstraints = false
stackViewLabelContainer.leadingAnchor.constraint(equalTo: scrollViewContainer.leadingAnchor).isActive = true
stackViewLabelContainer.topAnchor.constraint(equalTo: scrollViewContainer.topAnchor).isActive = true
addSubview(scrollViewContainer)
scrollViewContainer.translatesAutoresizingMaskIntoConstraints = false
scrollViewContainer.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: topAnchor, constant: 5).isActive = true
scrollViewContainer.heightAnchor.constraint(equalTo:stackViewLabelContainer.heightAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
scrollViewContainer.showsHorizontalScrollIndicator = false
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews()
{
super.layoutSubviews()
scrollViewContainer.contentSize = CGSize(width: stackViewLabelContainer.frame.width, height: stackViewLabelContainer.frame.height)
}
}
Here's the TableViewController
class TableViewController: UITableViewController {
override func viewDidLoad()
{
super.viewDidLoad()
tableView.register(TableViewCell.self, forCellReuseIdentifier: "reuse_cell")
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "reuse_cell") as! TableViewCell
return cell
}
override func viewDidLayoutSubviews()
{
print("called")
super.viewDidLayoutSubviews()
// let cells = tableView.visibleCells as! Array<TableViewCell>
// cells.forEach
// {
// cell in
// cell.scrollViewContainer.contentSize = CGSize(width: cell.stackViewLabelContainer.frame.width, height: cell.stackViewLabelContainer.frame.height)
//
// }
}
}
I figured out a method to make this work but it affects abstraction and it feels like a weird hack. You get the visible cells from within the UITableViewController, access each scrollview and update its contentSize. There's another fix I found by reversing dyld_shared_cache where I override draw method and stop reusing cells. Both solutions feel like they're far from "proper".
You should constraint the scrollview to the contentView of the cell.
contentView.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Now you can loop your labels and add them as the arranged subviews
for _ in 1...10
{
let labelView = UILabel();
labelView.backgroundColor = tintColor
labelView.textColor = .white
labelView.text = "ABCD 123"
stackView.addArrangedSubview(labelView)
}
I created a ViewController, and I want to add in my ViewController a container view, here I set the container view inside my ViewController:
var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
func setUpViews() {
view.addSubview(containerView)
containerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
Here I set my instance of SecondViewController in the containerView:
override func viewDidLoad() {
super.viewDidLoad()
setUpViews()
let secondViewController = SecondViewController()
secondViewController.willMove(toParent: self)
containerView.addSubview(secondViewController.view)
self.addChild(secondViewController)
secondViewController.didMove(toParent: self)
}
In my SecondViewController, I declared label and a view, I set the label in the center of the view:
let label: UILabel = {
let label = UILabel()
label.text = "Hello!"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
myView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
myView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
That's what I see in my app, but I aspected to see a label in the center of the gray view.
It doesn't work like I aspected and I don't understand why.
You need to set the frame and/or constraints on the loaded view:
override func viewDidLoad() {
super.viewDidLoad()
setUpViews()
let secondViewController = SecondViewController()
secondViewController.willMove(toParent: self)
containerView.addSubview(secondViewController.view)
// set the frame
secondViewController.view.frame = containerView.bounds
// enable auto-sizing (for example, if the device is rotated)
secondViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addChild(secondViewController)
secondViewController.didMove(toParent: self)
}
I am attempting to hide my navBar when swiped and has implemented navigationController?.hidesBarsOnSwipe = true at both viewWillAppear() and viewDidLoad() but the navBar remains unhidden. In my case, I have implemented a custom segmentedController below the navBar which toggles between two different tableViewControllers.
I am not sure if this is the reason why the navBar doesn't hide. My app looks like this, and the portion I want to hide is the 'Tickets' portion.
My code as such:
class TicketsViewController: UIViewController {
var upcomingTableViewController: UpcomingTableViewController!
var pastTransactionTableViewController: PastTransactionsTableViewController!
let segmentedControllerView: SegmentedController = {
let sc = SegmentedController()
sc.translatesAutoresizingMaskIntoConstraints = false
sc.segmentedController.addTarget(self, action: #selector(segmentedControlValueChanged), for: .valueChanged)
sc.segmentedController.selectedSegmentIndex = 0
return sc
}()
let containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnSwipe = true
}
override func viewDidLoad() {
super.viewDidLoad()
//These are the two tableViewControllers that are being toggled
upcomingTableViewController = UpcomingTableViewController()
pastTransactionTableViewController = PastTransactionsTableViewController()
setupNavigationBar()
setupViews()
}
#objc func segmentedControlValueChanged(_ sender: UISegmentedControl) {
let segmentedControl = sender
if segmentedControl.selectedSegmentIndex == 0 {
configureChildViewController(childController: upcomingTableViewController, onView: containerView)
} else {
configureChildViewController(childController: pastTransactionTableViewController, onView: containerView)
}
}
func setupNavigationBar() {
Helper.sharedInstance.setupNavigationBar(title: "Tickets", homeVC: self)
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.hidesBarsOnSwipe = true
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
}
func setupViews() {
view.addSubview(segmentedControllerView)
view.addSubview(containerView)
segmentedControllerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
segmentedControllerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
segmentedControllerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
segmentedControllerView.heightAnchor.constraint(equalToConstant: 44).isActive = true
containerView.topAnchor.constraint(equalTo: segmentedControllerView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
configureChildViewController(childController: upcomingTableViewController, onView: containerView)
}
func configureChildViewController(childController: UIViewController, onView: UIView?) {
var holderView = UIView()
if let onView = onView {
holderView = onView
} else {
holderView = self.view
}
addChildViewController(childController)
holderView.addSubview(childController.view)
constraintViewEqual(to: holderView, childControllerView: childController.view)
childController.didMove(toParentViewController: self)
}
func constraintViewEqual(to containerView: UIView, childControllerView: UIView) {
childControllerView.translatesAutoresizingMaskIntoConstraints = false
childControllerView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
childControllerView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
childControllerView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
childControllerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
}
The above code is my complete code for this ticketsViewController. Appreciate some advice why is the hideBarsWhenSwipe isn't hiding my navBar. Thanks.
Try resizing the element you have below to match the view controller height.
So I have a search icon as my right bar button item. When the user taps the icon, it allows the user to search and only show certain values in the tableview. It also hides the nav bar buttons at the top and the filterBar just below the navigation controller
func setupNavBarButtons() {
let searchImage = UIImage(named: "search_icon")?.withRenderingMode(.alwaysOriginal)
let searchBarButtonItem = UIBarButtonItem(image: searchImage, style: .plain, target: self, action: #selector(handleSearch))
navigationItem.rightBarButtonItem = searchBarButtonItem
setupFilterButton()
}
filter bar and navbar items to be hidden while searching like so:
func handleSearch() {
searchController.searchBar.isHidden = false
navigationItem.titleView = searchController.searchBar
searchController.searchBar.becomeFirstResponder()
navigationItem.rightBarButtonItems = nil
navigationItem.leftBarButtonItems = nil
filterBar.isHidden = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
And then it to reappear again once user stops searching, like so:
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupNavBarButtons()
searchController.searchBar.isHidden = true
filterBar.isHidden = false
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
// Also tried tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
}
Before Search:
During Search
After search: As you can see the tableview doesn't return to where it originally was. The filterBar is the gray view with 'Map' and 'Location'
Still got the same issue so I've uploaded my project here:
https://github.com/lukejones1/bug
At first, You added 'height' layout constraint with 40 px.
and you added 'height' layout constraint with 0 px when user clicked search button.
and again, you added 'height' layout constraint with 40 px when user clicked cancel button.
You need to reuse the layout constraint.
class ViewController: UIViewController {
var filterBarHeightLC : NSLayoutConstraint?
lazy var tableView : UITableView = {
let tv = UITableView()
tv.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
tv.layoutMargins = UIEdgeInsets.zero
tv.separatorInset = UIEdgeInsets.zero
tv.backgroundColor = .red
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
lazy var filterBar : UIView = {
let bar = UIView()
bar.backgroundColor = .blue
bar.translatesAutoresizingMaskIntoConstraints = false
return bar
}()
fileprivate lazy var filterButton : UIButton = {
let button = UIButton()
button.setTitleColor(UIColor.white, for: UIControlState())
button.setTitle("Filter", for: UIControlState())
button.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
fileprivate lazy var searchController: UISearchController = {
let sc = UISearchController(searchResultsController: nil)
sc.dimsBackgroundDuringPresentation = false
sc.hidesNavigationBarDuringPresentation = false
sc.searchResultsUpdater = self
sc.delegate = self
sc.view.tintColor = UIColor.white
sc.searchBar.tintColor = UIColor.white
sc.searchBar.delegate = self
return sc
}()
func setupNavBarButtons() {
let searchBarButtonItem = UIBarButtonItem(title: "Search", style: .plain, target: self, action: #selector(handleSearch))
navigationItem.rightBarButtonItem = searchBarButtonItem
setupFilterButton()
}
func setupFilterButton() {
let containerView = UIView()
containerView.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
containerView.addSubview(filterButton)
filterButton.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
filterButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
let filterBarButtonItem = UIBarButtonItem(customView: containerView)
navigationItem.leftBarButtonItem = filterBarButtonItem
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupNavBarButtons()
view.backgroundColor = .white
}
func handleSearch() {
searchController.searchBar.isHidden = false
navigationItem.titleView = searchController.searchBar
searchController.searchBar.becomeFirstResponder()
navigationItem.rightBarButtonItems = nil
navigationItem.leftBarButtonItems = nil
// changed!
filterBarHeightLC?.constant = 0
}
func setupViews() {
view.addSubview(filterBar)
view.addSubview(tableView)
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
filterBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
filterBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
filterBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true
// changed!
filterBarHeightLC = filterBar.heightAnchor.constraint(equalToConstant: 40)
filterBarHeightLC?.isActive = true
}
extension ViewController: UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate {
func filteredContentForSearchText(_ searchText: String, scope: String = "All") {
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupNavBarButtons()
searchController.searchBar.isHidden = true
// changed!
filterBarHeightLC?.constant = 40
}
func updateSearchResults(for searchController: UISearchController) {
filteredContentForSearchText(searchController.searchBar.text!)
}
}
Have a fun coding! :)