4

When using a UITableView and UISearchBar in swift, I was trying to find a way to keep the cancel button on the search bar enabled when the user searches something then scrolls in the table view, so they don't have to click twice to cancel the search. The default behaviour for resigning the first responder (or ending editing on the search bar) will gray out the cancel button, but I wanted to keep it enabled, so on Stackoverflow I found how to do this, but I can't find an answer online as to what searchBar.value(forKey: "cancelButton") does in the code below. Obviously it's somehow creating a reference to the cancel button on the search bar, but I don't understand what .value does (as I'm new to swift), and where the "cancelButton" key is coming from. I have read the func value(forKey key: String) documentation, but I still didn't understand it. It would be great if someone could explain what this is doing.

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        searchBar.resignFirstResponder()
        // If the user scrolls, keep the cancel button enabled
        if let cancelButton = searchBar.value(forKey: "cancelButton") as? UIButton {  // <-- This line
            if searchBar.text != "" { cancelButton.isEnabled = true }
            else { searchBar.showsCancelButton = false }
        }
    }

Thanks in advance.

Zemelware
  • 93
  • 8
  • https://stackoverflow.com/questions/4489684/what-is-the-difference-between-valueforkey-objectforkey-and-valueforkeypath Because `UISearchBar` has a "hidden" property named `cancelButton`. Side note, you are accessing a private variable, name was found with reverse engineering. It might be renamed in next iOS release or might get you rejected by the AppStore. – Larme Jun 22 '21 at 07:38
  • Do you want the cancel button always enabled whether you search or cancel search? is it right? – Pranjali Wagh Jun 22 '21 at 10:49

3 Answers3

2

UISearchBar is a subclass of

UIView -> UIResponder -> NSObject

And all NSObjects are conforming the NSKeyValueCoding Protocol Reference

valueForKey: is a KVC method. It works with ANY NSObject class and anything else that conforms to the above protocol. valueForKey: allows you to access a property using a string for its name. So for instance, if I have an Account class with a property number, I can do the following:

let myAccount = Account(number: 12)
myAccount.value(forKey: "number")

Since it is a runtime check, it can't be sure what the return type will be. So you have to cast it manually like:

let number = myAccount.value(forKey: "number") as? Int

I'm not going to explain the downcast and optionals here

So you can access any property of an object that conforms to NSKeyValueCoding just by knowing its method's exact name (that can be found easily by a simple reverse engineering).

Also, there is a similar method called performSelector that lets you execute any function of the object

⚠️ But be aware that Apple will reject your app if you touch a private variable or function of a system. (If they found out!)

⚠️ Also, be aware that any of these can be renamed without notice and your app will face undefined behaviors.

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
0

I was running into the same problem as you for searching through a list but I realized you can implement UITextfields instead...

Using didReturn to do textfield.resignFirstResponder() and when it resigns to take the value using textfield.value to search through a list.

0

In searchbar for iOS 12 or below, to access the elements you can use key value to access the elements. Like this function -

private func configureSearchBar() {
    searchBar.barTintColor = Color.navBarColor
    searchBar.makeRounded(cornerRadius: 5, borderWidth: 1, borderColor: Color.navBarColor)
    searchBar.placeholder = searchBarPlaceholderText
    searchBar.setImage(Images.search_white, for: .search, state: .highlighted)
    searchBar.setImage(Images.green_check, for: .clear, state: .normal)

    if let textField = searchBar.value(forKey: "searchField") as? UITextField {
        textField.textColor = .white
        
        textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
        
        let leftSideImage = textField.leftView as? UIImageView
        leftSideImage?.tintColor = .white
    }
    
    if let cancelButton = searchBar.value(forKey: "cancelButton") as? UIButton {
        cancelButton.setTitleColor(.white, for: .normal)
    }
}

Here by using the keys we are accessing the elements of the searchbar.

mefahimrahman
  • 197
  • 2
  • 6
  • 29