I'm currently working on a project where I have to filter through a NSArray (displayed in an UITableView). Problem is, there is neither a XIB file nor a storyboard.
My question is (I already got the predicate and search method et.c set up), how do I get the SearchBar to show up in my program only using code - and ofc I need to be able to work with it.
I'd be really glad about some help with this issue, because every tutorial I found either uses storyboards or xibs :(
Thank you very much up front!
Btw those are the two methods I got so far concerning the search bar, the TableView is also set up already.
-(void) filterContentForSearchText:(NSString *)searchText scope:(NSString *)scope {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#", searchText];
self.searchResults = [self.array filteredArrayUsingPredicate:predicate];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles]objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
You can do something like this:
In your .h add this:
#property (nonatomic,strong) UISearchBar *searchBar;
Add this in your viewDidLoad method
self.searchBar = [[UISearchBar alloc]init];
self.searchBar.frame = CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height);
self.searchBar.delegate = self;
//Customize the searchBar
[self.view addSubview:self.searchBar];
And whenever you want to search, press the search button within the keyboard. A delegate will be called when you do this. Like so,
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
[searchBar resignFirstResponder];
//Do some search
}
You can access the entered the text using the searchBar.text property. I am assuming that you have implemented the relevant protocols.
If there is no XIB file and no Storyboard, it is initiated somewhere in the code (probably in the viewDidload) looking something like this.
UISearchBar *searchBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0, 0, 100, 20)];
searchBar.delegate = self;
//etc..
Now first add a property in the interface
#property (nonatomic, strong)UISearchBar *searchBar;
And change this line in the viewDidLoad:
self.searchBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0, 0, 100, 20)];
Now you can us self.searchBar anywhere in your code.
Edit:
And don't forget to add this to the interface if it wasn't already:
#interface ViewController () <UISearchBarDelegate, UISearchDisplayDelegate>
Related
I'm trying to place a UISearchController within my apps UI. The layout is:
Yellow: a ViewController
Red: another ViewController
Black: a container within the YellowViewController
I want to put the UISearchView of the UISearchController within the black container.
My code is the following:
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsViewController];
UISearchBar* searchBar = self.searchController.searchBar;
searchBar.frame =_searchBarContainer.bounds;
[_searchBarContainer addSubview:searchBar];
[_searchBarContainer layoutIfNeeded];
It places the UISearchBar in the correct place:
But when I select the search field it expands the bar over the container's bounds:
How can I solve that and avoid the size/appearance change when selected?
Note: Tried some options playing with the translatesAutoresizingMaskIntoConstraints and the clipToBounds options with no success. I'm not an expert of iOS UI so I would appreciate an accurate answer. Thanks
According to my research, at each time you select SearchBar, a UISearchController is presented. This UISearchController always try to make searchBar's width equals to UIViewController which is presenting UISearchController.
My solution is when UISearchController makes SearchBar has wrong frame, set SearchBar'frame again. You can try this code below.
#interface ViewController () <UISearchControllerDelegate>
#property (nonatomic, strong) UISearchController* searchController;
#property (weak, nonatomic) IBOutlet UIView *searchBarContainer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsViewController];
UISearchBar* searchBar = self.searchController.searchBar;
self.searchController.delegate = self;
searchBar.frame =_searchBarContainer.bounds;
[_searchBarContainer addSubview:searchBar];
[_searchBarContainer layoutIfNeeded];
}
- (void)willPresentSearchController:(UISearchController *)searchController {
[searchController.searchBar addObserver:self forKeyPath:#"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)willDismissSearchController:(UISearchController *)searchController{
[searchController.searchBar removeObserver:self forKeyPath:#"frame"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (object == self.searchController.searchBar) {
if (!CGSizeEqualToSize(self.searchController.searchBar.frame.size, _searchBarContainer.frame.size)) {
self.searchController.searchBar.superview.clipsToBounds = NO;
self.searchController.searchBar.frame = CGRectMake(0, 0, _searchBarContainer.frame.size.width, _searchBarContainer.frame.size.height);
}
}
}
#end
At least, it works :)
Or
You can create another UIViewController which contains SearchBar after that add it to _searchBarContainer if your case don't have any problem with this.
Use UISearchBar and UITableView instead of UISearchController. It's easier to handle.
I have found useful infos.
There are several methods that are invoked when you tap the UISearchBar.
When some of this method are invoked, the frame of the UISearchBar change his value.
One of these methods try to fill the width equal to UIViewController.
Try to set your frame value inside one of these methods:
searchBarTextDidBeginEditing
searchBarShouldBeginEditing
In this way you override the default value.
Bye
I know that Apple deprecated the old Search Display Controller. I have a table with data, and I'm simply making a search bar that will allow the user to search for data (this data only contains alphabetical names) from the table view with a search bar. I was trying to use the following code at one point to detect if the user is typing something:
if(tableView == self.searchDisplayController.searchResultsTableView)
But I cannot because Apple has deprecated the old way. I understand that I have to use the UISearchController now, and I've looked at Apple's documentation and the sample code they provide, but I have not been able to understand it. I've looked everywhere, but have found no solid examples/tutorials on how to do this with objective-c. Can anyone please explain how we use the tableview in conjunction with the UISearchController in order to allow the user to search the data?
I have a simple demo project here
You need a TableviewController to show data,and a TableviewController to show searchResult
For example,(Code from demo project in the link)
Declare a SearchResultViewController
#interface SearchResultViewController : UITableViewController
Then in main tableview
#interface SearchTableViewController()<UISearchBarDelegate,UISearchResultsUpdating>
#property (strong,nonatomic)NSMutableArray * dataArray;
#property (strong,nonatomic)UISearchController * searchcontroller;
#property (strong,nonatomic)SearchResultViewController * resultViewController;
#end
In viewDidLoad,setup everything
self.resultViewController = [[SearchResultViewController alloc] init];
self.searchcontroller = [[UISearchController alloc] initWithSearchResultsController:self.resultViewController];
self.searchcontroller.searchBar.delegate = self;
self.resultViewController.tableView.delegate = self;
[self.searchcontroller.searchBar sizeToFit];
self.searchcontroller.searchResultsUpdater = self;
self.searchcontroller.dimsBackgroundDuringPresentation = NO;
self.definesPresentationContext = YES;
self.tableView.tableHeaderView = self.searchcontroller.searchBar;
Then in delegate method,do real search and update searchResultViewController
#pragma mark - search bar delegate
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar{
[searchBar resignFirstResponder];
}
#pragma mark - UISearchResultUpdating
//Do real search,this is up to you
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
NSString * searchtext = searchController.searchBar.text;
NSArray * searchResults = [self.dataArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
BOOL result = NO;
if ([(NSString *)evaluatedObject hasPrefix:searchtext]) {
result = YES;
}
return result;
}]];
SearchResultViewController *tableController = (SearchResultViewController *)self.searchcontroller.searchResultsController;
tableController.dataArray = searchResults;
[tableController.tableView reloadData];
}
Basically what I'm trying to achieve is to have my scope bar to never disappear.
Environment : IOS 7, storyboard, inside a view controller I have a "search bar and search display controller" and a separate tableview (the searchbar is not inside the table)
Inside the view controller.h
#property (nonatomic, strong) IBOutlet UISearchBar *candySearchBar;
Inside the view controller.m
#synthesize candySearchBar;
What I tried : inside a custom search bar class
- (void) setShowsScopeBar:(BOOL) showsScopeBar
{
if ([self showsScopeBar] != showsScopeBar) {
[super invalidateIntrinsicContentSize];
}
[super setShowsScopeBar:showsScopeBar];
[super setShowsScopeBar: YES]; // always show!
NSLog(#"setShowsScopeBar searchbar");
NSLog(#"%hhd", showsScopeBar);
}
and
searchBarDidEndEditing
Same thing in the view controller, but then
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[candySearchBar setShowsScopeBar:YES];
[candySearchBar sizeToFit];
}
I hope my question is clear, I tried many solutions posted all over the internet, most of them talk about the setshowsscopebar, but it doesn't seem to work. The output of the log in setshowscopebar is 1, but the scopebar is still not shown.
I still consider myself to be new to the code, the fault can still be a newbie mistake.
edit : another piece of code in the view controller, as you can see i'm searching blind:
-(void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller{
self.searchDisplayController.searchBar.showsCancelButton = YES;
self.searchDisplayController.searchBar.showsScopeBar = YES;
controller.searchBar.showsScopeBar = TRUE;
controller.searchBar.frame = CGRectMake(0, 149, 768, 88);
UIButton *cancelButton;
UIView *topView = self.searchDisplayController.searchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(#"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
//Set the new title of the cancel button
[cancelButton setTitle:#"Cancel" forState:UIControlStateNormal];
[cancelButton setEnabled:YES];
controller.searchBar.showsScopeBar = YES;
//candySearchBar.scopeButtonTitles = [NSArray arrayWithObjects:#"Flags", #"Listeners", #"Stations", nil];
}
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.frame));
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.bounds));
NSLog(#"%hhd#",controller.searchBar.hidden);
}
The code you tried will not work in iOS7 onward because apple has changed it behavior of UISearchBar to hide the scope when return to normal view. Add this method to your custom searchBar class.
-(void)layoutSubviews
{
[super layoutSubviews];
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
//Get search bar with scope bar to reappear after search keyboard is dismissed
[[[[self.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
[self setShowsScopeBar:YES];
}
}
Directly accessing object at index may crash the app in iOS6 because of difference in view hierarchy between iOS6 and iOS7, to avoid this, add this inside if condition only when its iOS7.
In addition this is also required in the custom search bar class
-(void) setShowsScopeBar:(BOOL)showsScopeBar {
[super setShowsScopeBar:YES]; //Initially make search bar appear with scope bar
}
I have the same issue. Perhaps it is something that has changed in iOS7 since showing the scope bar is supposed to be the default behaviour. You can verify this in the section "Creating an Optional Scope Bar to Filter Results" of the following tutorial:
http://www.raywenderlich.com/16873/how-to-add-search-into-a-table-view
Hopefully someone has a solution for this; otherwise we will have to look for a workaround.
initialize set scope bar NO
[self.searchBar setShowsScopeBar:NO];
[self.searchBar sizeToFit];
//default scope bar selection
self.searchBar.selectedScopeButtonIndex=3;
unselect/remove tick from scopeBar checkbox
It's possible (but hacky) to do this without a custom searchBar, in a pretty similar way to what CoolMonster suggests.
In your TableViewController, this will show the ScopeBar after a search ends:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
//Show the scopeBars
controller.searchBar.showsScopeBar = YES;
//Resize the searchBar to show ScopeBar
controller.searchBar.frame = CGRectMake(0, 0, 320, 88);
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
[[[[controller.searchBar.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
}
}
Then, since you probably want it to appear before you search, add this line to the TableViewController's viewDidLoad:
[self searchDisplayControllerDidEndSearch:self.searchDisplayController];
For the record, after getting this to work, I ended up using a separate segmented control instead of the approach above for several reasons, not least of which was that touching the ScopeBar of a SearchBar, once you get it to display, launches the search display tableView, which makes of sense if you're using it the recommended way. However, since I wanted the ScopeBar to work without launching the search tableview, for me it made more sense just to use my own segmented control and add it to my tableHeaderView under the searchBar.
So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
I have a UISearchBar. I want the the keyboard to go away as soon as user hits search...i did try resignFirstResponder but that didn't work. any help would be appreciated
- (void)viewDidLoad {
[super viewDidLoad];
self.title = NSLocalizedString(#"Songs", #"Search for songs");
NSMutableArray *array = [[NSArray alloc]initWithObjects: #"Book_1", #"Book 2", #"Book _ 4", nil];
self.booksArray = array;
[array release];
search.delegate=self;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
thanks
TC
Please make sure of following things.(I hope you are talking about search Button of keyboard.)
You have connected your IBOutlet of searchBar with its variable search.
You are not de-referring search variable anywhere in your code(reassigning it).
Your - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar method is in same viewcontroller as searchBar is in.
Please NSLog(#"Search Bar Instace : %#",search); in viewDidLoad method and searchBarSearchButtonClicked and verify that both instances are same if not then you are search is getting reassigned somewhere in code.
Just after [searchBar resignFirstResponder]; NSLog(#"isFirstResponder : %d",[searchbar isFirstResponder]); and NSLog(#"Next Responder : %#",[searchBar nextResponder]);
Is - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
method is being called when you tap search Button of keyboard? Make sure there are no keyboard responder(like textField,textView or other searchBar) is added behind your searchBar may be unintentionally. Please check it through xib also.
Thanks,
It should work by implementing the respond methods at the delegate object:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
Reference: http://www.iphonedevsdk.com/forum/iphone-sdk-development/7148-problem-uisearchbar-navigation-controller.html#post59467