0

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:

enter image description here

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!

1
  • You first add a text field to the view. You then add a table view to the view. So what do you expect? Do you change the order of appearance with insertSubview(_, at: )? Commented Jul 20, 2021 at 12:30

4 Answers 4

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

when I tried this I got this error: Thread 1: "Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600000d62080 \"UITextField:0x7fd5d7422f30.top\"> and <NSLayoutYAxisAnchor:0x600000d7f9c0 \"UIView:0x7fd5d7406bf0.top\"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."
@CartoonyAli - see the Edit to my answer.
1

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

Comments

0

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.

Result

4 Comments

I got an exception saying - Thread 1: "Can't add self as subview".
You should also transition your storyboard to UITableViewController
I'm not using storyboards, my UI is done through code only. How would I transition it then?
what do you mean by not using storyboards? there's a Main.storyboard file in your app. You may choose to layout your constraints through code but you're also using storyboards... The ArtistListViewController is connected to a ViewController through the storyboard, you should change that view to UITableViewController and connect it to your code accordingly
0

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..."

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.