Ok, ok. Day 2 was technically scheduled for 12/23, but I'm in a holiday spirit of sorts, so I took a few days off from blogging, but not coding. I'm riding high on a 6-day commit streak that's about to turn 1 week after today.
At any rate, the official Day 2 of ZQ:LoB introduces a number of updates. But more importantly, it weaves a cautionary tale into the #iosDevLife tapestry. In this case, we learn about the diligence that Apple sometimes has in updating it's technical documents.
UISeachDisplayController(iOS7) v. UISearchController(iOS8+)
I must say, what a huge difference a single deprecated method can make in terms of finding out appropriate documentation anywhere.
As the name may imply, the UISearchBarController/UISearchController are responsible for managing a search bar and it's associated functions, namely:
1. Displaying search results
2. Filtering results based on a query
3. Managing a search bar object and uitableview
So in it's essence, these two classes try to simplify the search functions that would require a number of UIKit classes, along with delegate methods, protocol definitions, notification handling, and view controller presenting/dismissing. Great, I'm all for simplifying. The only problem is that the pre-iOS8 version of this class is absurdly easier to understand its implementation than it's successor.
Let's do a quick comparison on the methods provided to instantiate the class (there are only 1 for each):
|(instancetype)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController||(instancetype)initWithSearchResultsController:(UIViewController *)searchResultsController|
I don't know about you, but it's very clear to me from the implementation of the
UISearchBarDisplayController that when instatiating the class, I pass it a
UISearchBar and a view controller to manage the search contents. But with the
UISearchController, there's no mention to the
UISearchBar and rather there is only a reference to a
searchResultsController (more on this in a second). So, what's going on exactly? Instatiating the UISearchController already creates its own instance of a UISearchBar. Which is great! But it's not really explicitly stated in the reference documentation for the class. sigh
Not only that, but the documentation never clearly explains what is meant by, or expected of, the
searchResultsController .. yea it's easy to gather that the view controller will conform to the
UISearchResultsUpdating protocols. But, unfortunately it doesn't mention that the
searchResultsController cannot be the same as the view controller that will be presenting the search bar. Catch that?
And because it's never explicitly stated, nor is it caught as a warning/error by the compiler, you end up tring to set up this
UISearchController with the same
UITableViewController that will house the search bar as its
searchResultsController. As a result, if you run your code, it will crash with an error of Application tried to present modal view on itself. And if you try to isolate the error and check out the view hierarchy at the time of the crash, you won't get back any relevant clues.
Well. What. The. Hell.
What is actually going on is that the
searchResultsController being passed to the
UISearchController in its
init method is going to end up being a modally presented view controller with a table view of live search results. So, by referencing the view controller that will house the search bar as the searchResultsController, the main VC tries to present itself whenever you start typing into the search bar.
Let me visualize this a bit for you:
There are three separate pieces to this puzzle:
1. A root UITableViewController
2. The UISearchController we are implementing
3. And a second UITableViewController who's only job is to display live search results after the UISearchBar becomes active
Thus, we're creating an entirely separate UITableViewController that will be managed by the root UITableViewController. On top of all this, the root UITableViewController has to manage the content for it's own table view, and that of the searchResults table view:
And of course, in hindsight this all makes sense. But numerous google searches did not yield the answers I was looking for.. and in fact most search results returned the implementation of a UISearchDisplayController, which is a lot easier to understand.
Then, one late evening, I came across this: Table Search with UISearchController in Apple's recently updated sample code. After many, many hours trying to force my way to a solution, I took a look at this sample code and figured out my problems in about 20 minutes. I guess I learned something that night.
Presenting the Search Bar
Now that we've established how everything is suppose to work together, we get figure out how the UI handles this all. Fortunately this part was a lot easier, but only because I stumbled upon the answer in the sample code mentioned above -- somewhat.
In the sample code, it makes a call to two methods that I have glossed over in the past:
setDefinesPresentationContext: (called on a VC)
sizeToFit (called on a view)
These two methods allowed for the phones UI to react to the UISearchbar as you would normally expect a search bar to work on your phone, namely shifting the elements around to fit in the search bar and presenting/dismissing the search results.
needed so that a presenting view will restrict the bounds of a presented view to the visible area of the presenting view. In this case, the
UINavigationController tells the
searchResultsController, what portion of the screen it can occupy. As a result, the presented search results are kept within the bounds of the currently visible window (The default is no, in which case the presented view keeps asking for a context up through the VC heirarchy until it reaches a VC that claims this role, or until it his the UIWindow). If this value is not set, then the filtered results table takes up the entire screen, completely covering the UINavigationController and UISearchBar.
sizeToFit was a special breed of headache.. so I wanted to place the searchbar into a navcontroller much the same way that the contact app does (side note, you can't).. The searchbar is placed into the tableHeaderView which then scrolls with the rest of the table. To do this, you call "sizeToFit" on the searchbar. According to Apple docs:
Call this method when you want to resize the current view so that it uses the most appropriate amount of space. Specific UIKit views resize themselves according to their own internal needs.
So from the sounds of it, the searchBar knows it's own size.. otherwise the headerview defaultsize determines it. Not entirely sure though, and I haven't tested this out yet.
And the last bit of magic in this stage was setting the contentOffset of the table to be -44pts so that the searchbar is hidden when the page loads, and also to add a couple of strings to the scopeButtonTitles array of the search bar so I could get a segemented control element with my searchbar that automagically displays and dismisses itself. Ain't that grand?
Be sure to check out ZQ:League of Blargl, V0.2 on Github