UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 35 Project 7 Challenge 2

Forums > 100 Days of Swift

Trying to search the json data and display the result of search but I can't figure out how to create an array based on the desired search term.

Here's what I'm thinking should work...

@objc func filterData() { let ac = UIAlertController(title: "Filter petitions by:", message: nil, preferredStyle: .alert) ac.addTextField() let submitAction = UIAlertAction(title: "Search", style: .default) { [weak self, weak ac] action in guard let answer = ac?.textFields?[0].text else { return } self?.submit(answer) } ac.addAction(submitAction) present(ac, animated: true) }

func submit(_ answer: String) {
    filteredPetitions = results.title.contains(answer)

}

3      

You need to filter the results array. So something like:

filteredPetitions = results.filter { $0.title.contains(answer) }

3      

Thanks, that got me past the compiler error but now my alert for getting the seach string doesn't appear to be working.

@objc func credits() { let ac = UIAlertController(title: "Credits", message: "Data from the White Houses We the People API (answer)", preferredStyle: .alert) present(ac, animated: true) }

@objc func filterData() {
    let ac = UIAlertController(title: "Filter petitions by:", message: nil, preferredStyle: .alert)
    ac.addTextField()
    let submitAction = UIAlertAction(title: "Search", style: .default) {
        [weak self, weak ac] action in guard let answer = ac?.textFields?[0].text else { return }
        self?.submit(answer)
    }
    ac.addAction(submitAction)
    present(ac, animated: true)
}

So this should show whatever is entered for searching in another alert but it doesn't and I can't get rid of the alert from the credits().

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

You don't add any buttons to your credits alert so there's no way to dismiss it. You need something like:

ac.addAction(UIAlertAction(title: "OK", style: .default))

Would need to see more of your code—especially the submit method—to know why your filter isn't working. If submit is just the one-liner you posted earlier, how does filteredPetitions get loaded into the table view?

3      

Duh, thanks.

Havn't got that far yet, just trying to make sure I'm getting what's typed into the textfield in filterData into the answer string.

3      

@macinlew - have you resolved this problem ? I also got stuck on this .. If you resolved this, could you share your code ?

3      

Hi! I have solved it but not in the way i would have wanted, and i don't understand part of it. Hope it helps you to solve yours:

In the viewController class i created two additional arrays: One that saves all the petitions (to reload them again later). One with the filtered petitions.

Then created a button using interface builder (was easier for my to configure the image of the magnifier there 😖). After that i defined the submit func inside viewDidLoad.

By the way, what roosterboy says, seems to be much more efficient than my for loop!

filteredPetitions = results.filter { $0.title.contains(answer) } 

Inside viewController Class:

var petitions = [Petition]()
  var allPetitions = [Petition]()
  var searchedPetitions = [Petition]()

      @IBAction func searchPetitions(_ sender: Any) {
      let ac = UIAlertController(title: "Search petitions containing", message: nil, preferredStyle: .alert)
      ac.addTextField()

      let submitAction = UIAlertAction(title: "Add", style: .default) {
                  [weak self, weak ac] action in
                  guard let answer = ac?.textFields?[0].text else { return }
                  self?.submit(answer)
              }

      let clearSearch = UIAlertAction(title: "Clear filters", style: .default) {
          [weak self] action in
          self?.petitions.removeAll(keepingCapacity: true)
          self?.petitions += self!.allPetitions
          self?.tableView.reloadData()

          // clearSearch loads again all the petitions, that have been 
          //saved in a separate array called allPetitions.

      }
              ac.addAction(submitAction)
              ac.addAction(clearSearch)
              present(ac, animated: true)
          }

Inside view did load

                func submit(_ answer: String) {
        allPetitions += petitions
        for item in allPetitions {
            if item.title.lowercased().contains("\(answer.lowercased())") {
                searchedPetitions.append(item)

                // I searched through the array for items containing 
                //the inserted string in the title. I used the lowercased 
                //method to avoid loosing some items due to the case 
                //sensitive criteria

            }

        }

        // I cleared the array that is shown in the table view, and added 
        //the found items there. I used a for loop, but as i said, i think that 
        //roosterboy's solution should be more efficient.

        petitions.removeAll(keepingCapacity: true)
        petitions += searchedPetitions
        tableView.reloadData()

}

But i have some questions:

First, how could i make it to reload everything when tapping the bottom tab, instead of having a button in the UIAlertController? I think it is more intuitive.

Second, why do i have to put the [weak self] action in line in clearSearch for it to work?

Then, how could i personalize the top buttons with a system image from SF symbols, without using the interface builder? There i used the questionMark for credits and the magnifier for the search button.

Finally, is there an easy way to change the array that is shown, instead of changing the content of one array?

Let me know if it helped you to solve it, if you would improve something in there, and thanks in advance for helping me!.

😁

3      

I decided to move on without completing this challeng but will come back later perhaps to try again.

I'm afraid I don't know how to answer any of your questions, sorry.

3      

Don't worry! I gave up on a couple of challenges too, but im sure that as we keep learning, they will become easier for us.

3      

Guys it's pretty straightforward, this is what I did. Of course I did not show the other functions not related to this question. And also do not forget to use your filteredPetitions array as your data for tableview.

class ViewController: UITableViewController {
    var petitions = [Petition]()
    var filteredPetitions = [Petition]()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Credits", style: .plain, target: self, action: #selector(credits))
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Filter", style: .plain, target: self, action: #selector(filter))

        let urlString: String

        if navigationController?.tabBarItem.tag == 0 {
            // urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
            urlString = "https://www.hackingwithswift.com/samples/petitions-1.json"
        } else {
            // urlString = "https://api.whitehouse.gov/v1/petitions.json?signatureCountFloor=10000&limit=100"
            urlString = "https://www.hackingwithswift.com/samples/petitions-2.json"
        }

        if let url = URL(string: urlString) {//The URL(string:) initializer is a failable one because you might type an invalid URL by accident.
            if let data = try? Data(contentsOf: url) {
                parse(json: data)
                filteredPetitions = petitions
                return
            }
        }
           showError()
    }

    @objc func filter() {
        let ac = UIAlertController(title: "Enter:", message: nil, preferredStyle: .alert)
        ac.addTextField()

        let submitAction = UIAlertAction(title: "Submit", style: .default) { [weak self, weak ac] action in
                             guard let answer = ac?.textFields?[0].text else { return }
                             self?.submit(answer)
               }
               ac.addAction(submitAction)
               present(ac, animated: true)
    }

    func submit(_ answer: String) {

      filteredPetitions.removeAll(keepingCapacity: true)
  for x in petitions {
            if x.title.contains(answer){
                filteredPetitions.append(x)
                tableView.reloadData()

3      

The UIAlertController with the text field to filter results was frozen. I wasn't able to write anything or press the "Submit" or "Cancel" buttom on it, until I closed Xcode and restart my computer. Now it seems to be working, but it takes around one minute to load body of the petition. I used several debugPrint() in order to try to figure out the reason and it happens after the webView.loadHTMLString(html, baseURL: nil).

Has anyone else experienced this?

This is my code in DetailVC code:

class DetailVC: UIViewController {

    var webView: WKWebView!
    var detailItem: Petition?

    override func loadView() {
        webView = WKWebView()
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        debugPrint("DetailVC starting to load")

        guard let detailItem = detailItem else { return }

        debugPrint(detailItem)

        let html = """
        <html>
        <head>
        <meta name="viewport" content="width=device-width, inicial-scale=1">
        <style> body { font-size: 150%; } </style>
        </head>
        <body>
        \(detailItem.body)
        </body>
        </html>
        """

        webView.loadHTMLString(html, baseURL: nil)

        debugPrint("HTML string loaded")

    }
}

And this is the output I am getting:

2021-03-17 13:30:06.698492-0500 project7[2336:198913] WF: === Starting WebFilter logging for process project7
2021-03-17 13:30:06.698707-0500 project7[2336:198913] WF: _userSettingsForUser : (null)
2021-03-17 13:30:06.698861-0500 project7[2336:198913] WF: _WebFilterIsActive returning: NO
"HTML string loaded"
2021-03-17 13:30:34.935577-0500 project7[2336:198913] [ProcessSuspension] 0x7febd60286b8 - TimedActivity::activityTimedOut:

3      

Also if you guys use the "filteredPetitions" array in this method, than you will be able to filter (damn this challenge is hard even me I've had to come on here, first I thought I needed 3 arrays, this was not easy but even if we look at each others code we still learn because at some point you can be on one challenge long time and still you did not figured it out then you need help because the main thing here is to understand the code and logic, overtime we all going to have a better logic and will be easier.)

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return filteredPetitions.count }

4      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.