Subclass UITableView for built-in search - objective-c

Would it be possible (or advisable) to attempt to subclass a UITableView to have built-in search functionality?
There are two reasons I wanna do this:
Reuse: I could use the same subclass at multiple places in my project with different data-sources.
Cleaner code: It would de-clutter my view controller. All the plumbing for implementing search would be neatly incapsulated in the subclass implementation.
Any ideas how one would go about doing this?

Use UISearchDisplayController
From Apple doc,
A search display controller manages display of a search bar and a
table view that displays the results of a search of data managed by
another view controller.
You initialize a search display controller with a search bar and a
view controller responsible for managing the original content to be
searched. When the user starts a search, the search display controller
is responsible for superimposing the search interface over the
original view controller’s view and showing the search results. The
results are displayed in a table view that’s created by the search
display controller. In addition to the original view controller, there
are logically four other roles. These are typically all played by the
same object, often the original view controller itself.
It can be implemented as,
searchController = [[UISearchDisplayController alloc]
initWithSearchBar:searchBar contentsController:self];
searchController.delegate = self;
searchController.searchResultsDataSource = self;
searchController.searchResultsDelegate = self;
UISearchDisplayController itself encapsulate all the normal search features. If you want you can subclass it to achieve whatever you are planning to do. You can use this in any class.

I guess I should ask for clarification: do you want the user's search to simply scroll to a row in the table, or do you want it to filter the rows, displaying only rows that match the search term?
Assuming you want it to filter the rows...
It would be pretty complicated to make a UITableView subclass to do this, because you'd need to interpose your own private UITableViewDataSource and UITableViewDelegate in front of the “user” data source and delegate.
It also might be rather inefficient, depending on how you get the data for the table view. If the table view can have thousands of rows, and they come from (say) a SQLite database or Core Data, the data source can search more efficiently for matching rows. In the table view, you'd have no choice but to iterate through the rows one by one, checking each for a match. Or you'd have to extend the data source protocol to give the table view a way to pass the search string to the data source... which seems like it defeats the goal of putting the searching in the table view.
If you tell us more about where the data comes from for your various table views, we might be able to give you better advice.

Related

Cocoa binding search field to tableview in a different nib

I've got a user interface that looks pretty much like iTunes. For purposes of encapsulation, the top area and the main table view are in separate classes with separate nibs. I want to bind the search field from the top view controller to the tableview in the bottom view controller. I've arranged it so there are properties to store the NSArrayController in both classes. The array controller is an array of dictionaries, and the dictionaries have a "search_keywords" key that I want to use to filter the tableview.
Is it possible to set up the search stuff in Interface Builder even though it's in a separate nib? I can't figure out what to put in the various boxes.
If it's not possible with IB, I assume it's possible in code, since there is a view controller with references to both of the sub-view controllers and I can get at the search field, table view and array controller objects through properties on the two classes.
How do I set it up? IB would be best, if it's possible.
What I did was use the NSSearchField in the top view controller as a dummy/placeholder. I create the "real" search field in the tableview's nib, and wire up all the bindings stuff as per normal. Then in the main view controller I grab the search field out of the tableview's nib, and replace the dummy searchfield with the real one using replaceSubview:with:
Now I can continue to use IB to modify the bindings, and it doesn't really matter what's in what nib as it all gets placed properly in the view hierarchy at runtime.
In your concept you cannot bind the search field in IB in the desired way.
Do it like
1. Create an accessor-method (accessorMethodForTextInSearchField or what name you want to use) in the TopClass for the Text in the searchField
2. Import the TopClass.h in the MainClass
3. In MainClass you can use
NSString *searchString = [ NSString stringWithString:[ TopClass accessorMethodForTextInSearchField] ];
4. Now search for searchString in the array

Two views of one controller presenting the same data in different ways - possible using storyboard?

I have a controller that will send a set of data to the view. This set of data must be viewable in two different ways (no, this is not about landscape/portrait), thus two different views. My question is, how do I create these two views, linked to one controller, using storyboard? I want to be able to see and edit both views without doing any ugly tricks.
In my experience it gets a bit messy when trying to deal with different "main" views in the context of one controller no matter what you do.
Basically you need to create another view right on top of your UIViewController's view in storyboard and make it hidden, connect its outlets to the controller and when a button that flips your presentation styles gets hit you need to either show or hide your second representation view like this:
- (void)btnAction:(id)sender {
self.secondView.hidden = !self.secondView.hidden;
}

Can the parent have less columns than child in NSOutlineView?

I'm trying to implement outline view in my app using NSOutlineView, but in my app the outer layer (parent) should have only one column (Brand) and inner layer (children) should have 5-6 columns (Size,Type,Image, etc.).
Is it possible to achieve, and how to do so if it is?!
Yes, you can have “full-width” cells for “group rows” in a NSOutlineView (or NSTableView).
If you’re using a cell-based outline view, implement outlineView:dataCellForTableColumn:item:
in your NSOutlineViewDelegate. Before this method is invoked with any of the existing columns, it will be invoked with a column of nil. For the corresponding rows, return a prototype NSCell, and in your other data source/delegate methods likewise return the corresponding information for a nil “column”. You just need to create a generic NSTextFieldCell for this; no need to style it yourself unless you want to. More information in the documentation or take a look at some Apple sample code.
If you’re using a view-based outline view, implement the equivalent outlineView:viewForTableColumn:item:. Unfortunately the documentation is currently pretty nonexistent, but the corresponding NSTableViewDelegate method is documented, and you can look at this code sample.
The appearance of the full-width item will vary based on the highlight style (selectionHighlightStyle) configured for your outline view; from your description, it sounds like you would want “regular” rather than “source list” behavior.

Is there a better way to load View Controllers from a table view?

This might be a very basic question, but I could not find the answer yet.
I have a UITableView that acts as a menu for my app. Each row on the table view, when selected, opens a different subclass of UIViewController.
At the moment my code works the same way used in the UICatlog example from Apple.
In the main view controller (the table view), each menu item is described in a dictionary in an array (menuList). Each dictionary contains an instance of the UIViewController subclass for that screen and other data about the menu item. When the user selects a row, the didSelectRow atIndexPath kicks in and calls the appropriate view controller, stored in the dictionary at that indexPath.row of the menuList array.
It seems to be very wasteful to alloc and init every single view controller when the table view first loads.
My question is: Is there a better way than the one demonstrated in UICatalog to alloc/init my view controller sublasses only when the associated row is tapped?
(I know I can use a complex if..else structure in the didSelectRow, but this results in an extremely long didSelectRow method and breaks encapsulation. I wonder if there is a cleaner way to do this, allocing and initing the appropriate view controller based on data from the dictionaries)
user1349768 try to use Storyboard, but this feature only works in iOs 4 and higher.
Just a suggestion ... put some reference to each view controller into NSArray and then initiate and segue to them when the row gets tapped on (and just get the reference from objectAtIndex:).
Although I could not find a better way to do this, the memory signature of each allocated View Controller is only 288 bytes. Since the solution suggested by apple is a lot more elegant and scalable then using a switch case statement, I left it as it is.

Adding a new cell to a Table View from a different view

I was wondering if there is a way i could have a table view displayed on one view then when you hit the "add" button it would flip you to a second view where you would type in the name that you want the new cell's text label to say. Then you would hit a "done" button and it would flip you back to the first view where it would have the newest item. And you would be able to add however many cells you want.
Cocoa and Cocoa touch are based on an MVC pattern. What that means is there's separate layers each with responsibilities - the model, the view and the controller.
The model is the data and the operations on that data, the view is what you see and the controllers mediate between them.
So, in your example, you wouldn't add a cell from a different view. That violates the fundamental pattern. Instead, what would happen is the view where your adding the data would call to the model to add the item and the view would go away. Then, when it returns to the view that lists the items, it would query the model (which contains the new object) and the list would contain the object.
The two views are decoupled and they share the same model. It allows you to change interfaces and storage without breaking most of your app because they're decoupled.
For example, you could have a table view controller that gets it's list from querying your model class. Your model class could be a singleton ( [MyModel sharedInstance] ) that offers a method like:
NSArray* items = [model getItems];
That method could be backed by Sqlite (search for fmdb), CoreData storage, a simple file, or even in memory data like an NSMutableArray. Your UITableViewController implements the callback methods by calling into your model.
Then, you offer an Add button which calls this to modally show your AddItemController.
[self presentModalViewController:addItemController];
After the user supplies the data on the form and clicks the Done/Save button, you call your model to save the item which is a class with the data:
[[MyModel sharedInstance] saveItem:item];
That writes to your storage.
Then, upon return to your UITableViewController, in viewWillAppear, you re query the data and call for the table view to reloadData;
_items = [[MyModel sharedInstance] getItems];
[[self tableView] reloadData];
Now the table shows the data you just added.
There's other variations but that's a basic one with MVC separation.
I'd just like to add, putting this in plain words...
Whatever source that you use for your table to load cells from (an array, dictionary, class, you name it...) then you create the "add" button, make it push the new view controller, then when you press the "done" button on your navigation bar, add the object's property (the name text field info, or whatever you need) to the source you load your table view data from.