How can I use UISearchBar or UISearchDisplayController as a UICollectionView header? - iphone

I have a UICollectionView which has nine cells in it. Each cell is a square, so that they're laid out in a 3 by 3 grid. This works nicely, and when the device rotates, I call performBatchUpdates:completion: to force the collectionView to layout the cells for the new orientation.
Here's what it looks like without the search bar:
I'm now trying to add a UISearchBar on top of the collectionView, like so:
I've tried adding it as a header view, but instead of appearing on top, it consumes the entire screen, and the cells are pushed off to the right. To see them, you scroll over, where there appear as they do without the search bar.
To add the search bar, I've tried two approaches. Neither works completely, and one seems like terrible practice. The first way I've tried is to add the search bar as a header view. My main view is a subclass of UICollectionViewController. Here's how I set things up:
- (void) viewDidLoad
{
// Set up some other things...
//
// Register the header view class
// which installs a search bar inside
// of itself.
//
[[self collectionView] registerClass:[PDReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"header"];
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PDReusableView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"header" forIndexPath:indexPath];
[header setFrame:[[[self navigationController] navigationBar] bounds]];
return header;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return CGSizeMake(collectionView.frame.size.width, self.navigationController.navigationBar.frame.size.height);
}
return CGSizeZero;
}
Inside of PDReusableView, I install a UISearchBar. This is what I get:
So this approach fails, because I can't wrangle a search controller or search bar into a reusable view. If I can't put it into a reusable view, it can't go into a UICollectionView.
My other option is to resize the collection view, so it doesn't occupy the entirety of the screen. The question then becomes, where do I install the search bar in the view hierarchy? The simplest place, although likely incorrect, is the app delegate, where I set up the rest of the view hierarchy. I managed to get the search bar to appear above the collection view by installing it inside of application:didFinishLaunchingWithOptions: Here's my code for that:
UICollectionView *collectionView = [[self mainMenuViewController] collectionView];
CGRect f = [collectionView frame];
CGFloat height = [[[self navigationController] navigationBar] frame].size.height;
f.size.height -= height;
f.origin.y += height;
[collectionView setFrame:f];
// Cause the cells to be laid out per the new frame
[collectionView performBatchUpdates:nil completion:nil];
[[collectionView superview] addSubview:[self searchBar]];
[[self searchBar] setFrame:[[[self navigationController] navigationBar] bounds]];
While I could proceed to add search logic to the app delegate, it would be inappropriate to do so, because I'd be maintaining a dataset, filtered search results, and a lot of other logic in the app delegate. I feel like the UICollectionViewController subclass is a much more appropriate place to put it.
Is there a way to install the UISearchBar in a UICollectionView without using Interface Builder? How?
Edit:
I'm using a vanilla UICollectionViewFlowLayout and using some of the delegate methods to set the size of cells etc.

I haven't used UICollectionView or UICollectionViewController yet, but I have used UITableView and UITableViewController, and my general rule of thumb is: never use UITableViewController if you want to do anything complicated. I suspect that UICollectionViewController is similar: a fairly brittle, limited view controller that doesn't actually save you much hassle.
So if I were in your position, I'd be looking at just subclassing UIViewController and including a UICollectionView in my view controller. Your view hierarchy might then be as simple as:
Root UIView
UISearchBar
UICollectionView

Related

iphone development: how to put a UIView over UITableView [duplicate]

This question already has answers here:
How to add a UIView above the current UITableViewController
(21 answers)
Closed 8 years ago.
In my app I use storyboard. One of my elements in the storyboard is a UITableViewController. So it has a tableview inside of it.
My question is how can I put a UIView over this tableview. It is gonna be hidden and I want to make it visible when a tableviewcell in the tableview is pressed. Is that possible? How can I do that?
The best solution is use normal view controller (UIViewController) in StoryBoard. Your controller will need to implement two protocols (UITableViewDataSource and UITableViewDelegate) and you will need add UITablewView in the view controller's view.
In this case in interface builder you will be able to put any view in the view controller's view (can put it above table view).
Use tableHeaderView property.
Returns accessory view that is displayed above the table.
#property(nonatomic, retain) UIView *tableHeaderView
The table header view is different from a section header.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html
Lets assume your view to be hidden/shown on top of table view is topView, declared as a global variable.
Declare topView in .h as
UIView *topView;
Now Lets assume that you have the UITableViewController object as tableViewController then, initialize the topView in viewDidLoad of tableViewController class
-(void)viewDidLoad
{
topView=[[UIView alloc] initWithFrame:yourNeededFrameSize];
[self.tableView addSubview:topView];//tableView is a property for UITableViewController inherited class
topView.hidden=YES;//Hide it initially for the first time.
}
assuming that you have the UITableViewDelegate methods implemented here is what you will do in didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(topView.isHidden==YES)
{
topView.hidden=NO;
}
else
{
topView.hidden=NO;
}
}
hope it helps.
you can also get view into front.
[view bringsubviewtofront];
I had a similar problem where I wanted to add a loading indicator on top of my UITableViewController. To solve this, I added my UIView as a subview of the window. That solved the problem. This is how I did it.
-(void)viewDidLoad{
[super viewDidLoad];
//get the app delegate
XYAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
//define the position of the rect based on the screen bounds
CGRect loadingViewRect = CGRectMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2, 50, 50);
//create the custom view. The custom view is a property of the VIewController
self.loadingView = [[XYLoadingView alloc] initWithFrame:loadingViewRect];
//use the delegate's window object to add the custom view on top of the view controller
[delegate.window addSubview: loadingView];
}

UITableViewController - limit the scrolling

I have a UITableViewController which contains a View and a Table View Section.
View contains a label that indicates the title of the table.
My problem is that the scroll includes the View. What I want is to keep View static (exclude from scrolling) and to scroll only Table. (I use static cells)
Thanks.
The hierarchy of a UITableViewController is
- UIView
-- UIScrollView
---- UITableView
Initially you're in the UITableView when modifying items, so you'll want to add the portion that you do not want to scroll to the UIView (outside of our scrollView). So you'll need to call super a couple times like this:
[self.superview.superview.view addSubview:viewThatDoesNotScroll];
Since UITableView is a subclass of UIScrollView:
- (void)viewDidLoad {
[super viewDidLoad];
// mySubview is an instance variable, declared in .h file
[self.tableView addSubview:mySubview];
// here goes the rest of your code
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(scrollView == self.tableView) {
mySubview.frame = CGRectMake(mySubview.frame.origin.x, scrollView.contentOffset.y, mySubview.frame.size.width, mySubview.frame.size.height);
}
}
The code was taken from WWDC '10 or '11 (I don't remember), so I'm sure it's the most appropriate way to do it.
Explanation: In -viewDidLoad you create your view and add it as a subview of your tableView. You can do it in -loadView or -init - it doesn't matter. The most important lines are in the -scrollViewDidScroll: method. This method is called whenever user drags the scrollView, so you can simply set the origin.y of your subview to contentOffset.y of the scrollView.
Do not UITableViewController. Use UIViewController and manage the views outside of the UITableView object. If you need, you can also implement UIViewControllerContainment to manage different views and different view controllers inside your custom view controller.

Resize table view

I'm searching for a way to have a UITableViewController with a UITableView at the top and a UIPickerView bellow (with fix position).
I've found a solution for fixing the picker with the code bellow:
- (void)viewDidLoad {
[super viewDidLoad];
_picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
_picker.showsSelectionIndicator = YES;
_picker.dataSource = self;
_picker.delegate = self;
// Add the picker to the superview so that it will be fixed
[self.navigationController.view addSubview:_picker];
CGRect pickerFrame = _picker.frame;
pickerFrame.origin.y = self.tableView.frame.size.height - 29 - pickerFrame.size.height;
_picker.frame = pickerFrame;
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height = 215;
self.tableView.frame = tableViewFrame;
[_picker release];
}
The problem is with the tableview, it seems resizing doesn't work so I can't see all results .
Thanks for your advice.
You should use a UIViewController subclass instead of UITableViewController to manage a table view if the view to be managed is composed of multiple subviews, one of which is a table view. You can add a UITableView subview and make your controller implement UITableViewDelegate and UITableViewDataSource protocols.
The default behavior of the UITableViewController class is to make the table view fill the screen between the navigation bar and the tab bar (if either are present).
From Table View Programming Guide for iOS:
Note: You should use a
UIViewController subclass rather than
a subclass of UITableViewController to
manage a table view if the view to be
managed is composed of multiple
subviews, one of which is a table
view. The default behavior of the
UITableViewController class is to make
the table view fill the screen between
the navigation bar and the tab bar (if
either are present).
If you decide to
use a UIViewController subclass rather
than a subclass of
UITableViewController to manage a
table view, you should perform a
couple of the tasks mentioned above to
conform to the human-interface
guidelines. To clear any selection in
the table view before it’s displayed,
implement the viewWillAppear: method
to clear the selected row (if any) by
calling deselectRowAtIndexPath:animated:.
After the table view has been
displayed, you should flash the scroll
view’s scroll indicators by sending a
flashScrollIndicators message to the
table view; you can do this in an
override of the viewDidAppear: method
of UIViewController.

Transparent View at the Top of a UITableView

When you open Mail on an iPhone and tap Edit, select an email and tap Move, a UITableView appears with all the folders you can put the mail in. At the top is a transparent view that shows the email you selected. When you move your finger down, the view stays in place, when you move it up, the cells are visible through the transparent view.
How did apple configure this view? I thought of two ways, but they both don't work:
The view is returned as the header view of the UITableView. Doesn't work because the view stays at the top even if the table view is moved down.
The view is static at the top, the frame of the table view starts at the bottom of the transparent view. This doesn't work because when the table view is moved up, it is visible through the transparent view.
Any ideas on how to recreate this effect?
You need to create your transparent view and then add it as a subview to the view controller so it's a sibling of the UITableView. You would do this in the viewDidLoad() method.
- (void)viewDidLoad
{
int viewHeight = 50;
// Assume myTableView is a UITableView variable
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 44) style:UITableViewStylePlain];
myTableView.scrollIndicatorInsets = UIEdgeInsetsMake(viewHeight, 0, 0, 0);
myTableView.contentInset = UIEdgeInsetsMake(viewHeight, 0, 0, 0);
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.view.frame.size.width,viewHeight)];
// Configure your view here.
myView.backgroundColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.8 alpha:0.75];
[self.view addSubview:myTableView];
[self.view addSubview:myView];
[myView release];
}
You could also setup your view using an XIB, but I'll leave that as an exercise for you.
Edit: Removed the requirement for the the UITableView delegate methods and custom header view by using the contentInset property instead.
Note: see additional comments below.
For the XIB method:
Create a new UIViewController (with XIB)
Add to it your UITableView and your UIView.
Make them both subviews of the default UIView (called View) and make sure that they are added in the correct order. UITableView should be the first in the list, your view should be second (so your view is on top of the UITableView).
In the .m file implement the minimum for UITableViewDataSource and UITableViewDelegate:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
In Interface Builder select the UITableView list and go to 'Connection Inspector'. There drag dataSource and delegate to 'File Owner'.
Save and run. You should be done now.

How do I display a placeholder image when my UITableView has no data yet?

I have an application that, on load, displays a UITableView with the user's data in it.
However, when the user first loads the application (before they've created any data), I'd like to display, instead of an empty table, a background image (with an arrow pointing to the 'add a record' navbar button). Once the user has added their first record, the tableview is displayed instead. I've seen numerous apps do this - the only example I can think of at present is Cha-Ching, before you have any budgets/etc set up. I can't for the life of me work out how to do this, though.
I initially added a navigationcontroller to the main window's xib, the rootViewController of which was a custom viewcontroller/xib. This rootViewController contained the background image with a hidden tableview above it, and a custom tableviewcontroller that managed the tableview. This seemed to work just fine, and if there was data it would load and display in the table. However, if I was to scroll the data offscreen, the app would crash, with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I have no clue what a UITextEffectsWindow is, or why it was trying to manage my tableview. I presume something may be hooked up incorrectly in my view hierarchy...
If there's a much simpler/more straightforward way of doing this, I'd be very grateful if someone could explain it. How would you do this?
Thanks in advance.
Here's one solution that I've been satisfied with so far.
First, I created a transparent view that was the same size as my TableView. I add this view as a sibling of the TableView whenever the TableView's data source has no data in it. I completely remove the view when data is available because transparency can affect the smoothness of the scrolling animation with TableViews.
I simply added a transparent label to this view that says something to the effect of "No Data Available". Adding a little shadowing to this label really helped to reinforce the concept that the label was 'floating' over top of the empty TableView.
I like your method of using an image though. Done properly, it might save you some work if you don't have to localize a string like I currently do.
To achieve this using a UITableViewController subclass as my only view (within a UINavigationController as per the Apple template) I used the following approach:
Create a UIView of the size of my tableView in the XIB that contains your UITableViewController and tableView.
Add an ImageView set with my placeholder image to the UIView.
Wire up the UIView as an IBOutlet (in the example code below, I called it emptyTableView)
When it is time to show the placeholder from within the UITableViewController subclass :
[self.tableView addSubView:emptyTableView];
[self.tableView setScrollEnabled:FALSE];
Disabling the scroll is necessary otherwise the user will be able to move the placeholder image up and down. Just remember to enable it once the user adds an item.
To remove the image view
[emptyTableView removeFromSuperview];
To do this, I use the following controller instead of UITableViewController. It will automatically place a view over the table when it is empty, and remove it when it is filled.
Just call [self reloadData] instead of [self.tableView reloadData] so that it can check if the table became empty.
In your subclass, implement a makeEmptyOverlayView function that will create the view to show over an empty table.
#interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
#end
#implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: #selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: #selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
#end
If you use Three20, you can easily set any image you want as a place holder prior to your table being populated.
So, to solve this I did as discussed in the comments above:
I created a normal UIViewController subclass, which contained a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols, and looks after the tableView. The viewController class simply shows or hides the imageView depending on whether data is available.
I was going wrong before by trying to manage both these views with a UITableViewController. A UITableViewController has to have a tableView as its view, whereas, with this solution, a viewController can contain both the image and the tableView, and implement the necessary protocols to manage the tableView.
Thanks for all the help!