UISearchDisplayController Without Dimming? - ios

I am writing a SplitView iPad app. Inside the DetailViewController, there's a little view that contains a UITableView and a UISearchBar and its controller. This view does not represent the whole screen space reserved for the DetailViewController. Actually, it uses just half of it. There's an UIImageView on the other half.
And this is where trouble comes in: every time I use the search bar, the displaycontroller (I assume) dims everything present inside the DetailViewController, including the image view. That is not consistent with what someone would expect when running the app. Is there any way to set the frame to be dimmed? Or at least disable dimming for good?
Thanks in advance.

You are correct that it is the UISearchDisplayController that is managing the "dimming" effect that you're seeing.
What the UISearchDisplayController is doing is adding a UIControl as a subview to the view of the searchContentsController (a property of UISearchDisplayController), which is likely your detail-view controller. This UIControl is just an alpha'd view with a gray background. It seems to have a touch-up-inside event handler that ends searching when tapped.
To constrain the dimming effect to your sub-view of the detail-view, you need to do three things. (I'm assuming your detail-view-controller is defined via a xib. If not, these steps can be done in code too.)
1) add a new UIViewController to your detail-view-controller xib. Attach this new view-controller to an IBOutlet of your detail-view-controller. In my example I call this "_searchAreaViewController". This is important, even if you wont ever access the view controller (but remember, you'll have to release it at some point)
#interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate, UITableViewDelegate, UITableViewDataSource> {
UIPopoverController *popoverController;
UIToolbar *toolbar;
id detailItem;
UILabel *detailDescriptionLabel;
IBOutlet UIViewController* _searchAreaViewController;
}
2) make the containing view for your search area the view of this new view-controller. To do this, use Interface Builder to set a new referencing outlet for this view by dragging the outlet to the searchAreaViewController and selecting the "view" outlet. You must have a containing view - it should be a subview of your detail-view, and it should contain the UISearchBar and likely your UITableView.
3) make the searchContentsController property of the UISearchDisplayController refer to this new view controller instead of the detail-view-controller. This can only be done via Interface Builder as the property is read-only (IB has some magic to make this work?) If you need to do this step via code you'll have to subclass the UISearchDisplayController and return the correct value from a property override of "searchContentsController".
I made a sample app to demonstrate this and the only line of code I had to add to the SplitView template was the one listed in step 1 above. Everything else was just adding the views/controllers and connecting them properly in IB.
good luck!

iOS 8+
[[UIView appearanceWhenContainedInInstancesOfClasses:#[NSClassFromString(#"UISearchDisplayControllerContainerView")]] setHidden:YES];
iOS 7
[View appearanceWhenContainedIn:NSClassFromString(#"UISearchDisplayControllerContainerView"), nil] setHidden:YES];
I know, that UISearchDisplayController is deprecated for now, but if you still need to use it, you can solve your issue with one line of code perfectly. Add it to viewDidLoad method.

Could you clarify what you mean by "use the search bar" and "dims everything present"? I interpret what you wrote in such a way that the keyboard pops up when you are about to enter text in the text field of the search bar. And that at this point the detail view is dimmed out, preventing user interaction.
The cause is that the search bar implements a modal dialog which prevents user interaction with the view as long as the keyboard is shown. Unfortunately, there doesn't seem to be any way to configure the search bar to prevent this behavior. On the other hand I am not sure that the user won't expect this behavior since search bars are modal consistently and behave like this in general under iOS.
I have tried two work-arounds:
1.) There is a property of the UIViewController called modalPresentationStyle which produces exactly the behavior you describe if it has the value UIModalPresentationFormSheet ("All uncovered areas are dimmed to prevent the user from interacting with them.", see the Apple documentation). But setting this property to a different values does not change the result (at least for me it didn't work).
2.) You would need to write your own non-modal search bar replacement since a standard UITextField is non-modal and thus does not dim out any other UI elements. This approach works, but you might need a little more work to make it look like a "regular" search bar. But, again, since this search bar behaves differently from the modal normal search bars in iOS this might not really be what the users expect.

I know I am late and this is a horrible idea here, but 'setHidden:No' did not work for me.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
BOOL hasBeenremoved = NO;
hasBeenremoved = [[[[NSThread mainThread] threadDictionary] objectForKey:#"hasBeenremoved"] boolValue];
if (hasBeenremoved)
{
UIView* dimmingView = nil;
dimmingView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingView"];
UIView* dimmingViewSuperView = nil;
dimmingViewSuperView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingViewSuperView"];
[dimmingViewSuperView addSubview:dimmingView];
[[[NSThread mainThread] threadDictionary] setObject:#NO forKey:#"hasBeenremoved"];
}
if ([searchText length] == 0 || [searchText isEqualToString:#""] )
{
[searchBar becomeFirstResponder];
[[[self primarySearchDisplayController] searchResultsTableView] reloadData];
[[[self primarySearchDisplayController] searchResultsTableView] setHidden:NO];
for( UIView *subview in self.view.subviews )
{
if([subview isMemberOfClass:[UIControl class]] ||
([[[subview class] description] isEqualToString:#"UISearchDisplayControllerContainerView"]))
{
for(UIView *subView2 in subview.subviews)
{
for(UIView *subView3 in subView2.subviews)
{
if (subView3.alpha < 1)
{
if ([[[subView3 class] description] isEqualToString:#"_UISearchDisplayControllerDimmingView"])
{
[[[NSThread mainThread] threadDictionary] setObject:subView3 forKey:#"dimmingView"];
[[[NSThread mainThread] threadDictionary] setObject:subView3.superview forKey:#"dimmingViewSuperView"];
[[[NSThread mainThread] threadDictionary] setObject:#YES forKey:#"hasBeenremoved"];
[subView3 removeFromSuperview];
}
}
}
}
}
}
}
}

Related

Move UINavigationController's toolbar to the top to lie underneath navigation bar

When you create a UINavigationController, you can reveal its default hidden UIToolbar via setToolbarHidden:animated: (or by checking Shows Toolbar in Interface Builder). This causes a toolbar to appear at the bottom of the screen, and this toolbar persists between pushing and popping of view controllers on the navigation stack. That is exactly what I need, except I need the toolbar to be located at the top of the screen. It appears that's exactly what Apple has done with the iTunes app:
How can one move UINavigationController's toolbar to the top to lie underneath the navigation bar instead of at the bottom?
I've tried to implement the UIToolbarDelegate, override positionForBar:, and return UIBarPosition.TopAttached or UIBarPosition.Top after setting the delegate of self.navigationController?.toolbar to self, but this did not even call the delegate method therefore it didn't change the bar position.
Note that I need the toolbar to be preserved between navigation, so I can't simply add a toolbar to a view controller and position it under the nav bar.
The solution for this problem is a two (and a half) step process:
First you have to add an observer to the toolbars 'center' member.
Second, inside your observeValueForKeyPath:ofObject:change:context:, relocate the toolbar to your target position every time it is moved by somebody (e.g. the navigation controller itself for example, when the device rotates).
I did this in my UINavigationController subclass.
To avoid recursion, I've installed an local flag member 'inToolbarFrameChange'.
The last (half) step was a bit tricky to find out... you've to access the toolbars 'frame' member, to get the observer to be called at all... I guess, the reason for this might be, that 'frame' is implemented as an method inside UIToolbar and the base 'frame' value in UIView is only updated when the UIToolbar method is called ?!?
I did implement this 'frame' access in my overloaded setToolbarHidden:animated: method, which does nothing but to forward the call and to access the toolbars 'frame' value.
#interface MMMasterNavigationController ()
#property (assign, nonatomic) BOOL inToolbarFrameChange;
#end
#implementation MMMasterNavigationController
/*
awakeFromNib
*/
- (void)awakeFromNib {
[super awakeFromNib];
// ... other inits
self.inToolbarFrameChange = NO;
}
/*
viewDidLoad
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 'center' instead of 'frame' from: http://stackoverflow.com/a/17977278/2778898
[self.toolbar addObserver:self
forKeyPath:#"center"
options:NSKeyValueObservingOptionNew
context:0];
}
/*
observeValueForKeyPath:ofObject:change:context:
*/
- (void)observeValueForKeyPath:(NSString *)pKeyPath
ofObject:(id)pObject
change:(NSDictionary<NSString *,id> *)pChange
context:(void *)pContext {
if ([pKeyPath isEqualToString:#"center"]) {
if (!self.inToolbarFrameChange) {
//NSLog(#"%s (0): %#", __PRETTY_FUNCTION__, pChange);
self.inToolbarFrameChange = YES;
CGRect tbFrame = self.toolbar.frame;
// maybe some other values are needed here for you
tbFrame = CGRectMake(0, 0, CGRectGetWidth(tbFrame), CGRectGetHeight(tbFrame));
self.toolbar.frame = tbFrame;
self.inToolbarFrameChange = NO;
}
} else {
[super observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext];
}
}
/*
setToolbarHidden:animated:
*/
- (void)setToolbarHidden:(BOOL)pHidden
animated:(BOOL)pAnimated {
FLog;
[super setToolbarHidden:pHidden animated:NO];
// Access the 'frame' member to let to observer fire
CGRect rectTB = self.toolbar.frame;
rectTB = CGRectZero;
}
You may create not UITableViewController but UIViewController. In view of UIViewController place UIToolBar below NavigationBar and UITableView. Delegate all necessary list of UITableView to UIViewController and thats all.
Why should you use UIViewController instead UITableViewController? Because tableView will not have statical positions elements. You should have something that not contains ScrollView. In this situation it is only UIView. Also you may do some hack with UIScrollView of tableView but I think described method is easer.

IOS7 : uisearchdisplaycontroller always show scope bar

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.

iOS get which uielement invoked UIKeyboardWillShowNotification

In my iOS app I have several UIElements that can process user input: textfields, editable webviews, etc. each time I write something into these UIElements the keyboard (obviously) will come up. Before it happens I can catch this event by observing the UIKeyboardWillShowNotification.
I would like to know what's the way to find out which UIElement invoked this action.
Thanks for your help!
The keyboard is launched when the view tapped by a user is set as FirstResponder, so I think this question is the equivalent of saying how do I get the current first responder when UIKeyboardWillShowNotification is received?.
The answer to that question by Thomas Muller was to use a class extension along the lines of:
#implementation UIView (FindFirstResponder)
- (UIView *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIView *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
#end
So I think you could use that inside your handler for UIKeyboardWillShow to figure out what caused it.
I think the section 4 (Moving Content That Is Located Under the Keyboard) of this document can give you a hint about knowing wich element has the keyboard.
http://developer.apple.com/library/ios/#DOCUMENTATION/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW1

What is the correct usage of the UISearchBarDelegate showsSearchResultsButton attribute?

I configure my search bar to show the results button, but the button only shows until the user enters a character. At that point, the "X" cancel button replaces it. So without entering characters, the search result set equals the entire data set. I'd like the results button to stay there so when the user has typed enough characters to get a smaller result set (like 5 or 6 rows), they can click the results button, my delegate will get called, and I can show just that result set.
UISearchBar * theSearchBar = [[UISearchBar alloc]
initWithFrame:CGRectMake(0,0,700,40)];
theSearchBar.delegate = self;
theSearchBar.placeholder = #"What are you looking for?";
theSearchBar.showsCancelButton = NO; // shows up after first char typed.
theSearchBar.showsSearchResultsButton = YES; // disappears just when I need it.
...further down in the VC... this method can only called when the search bar's input field is empty.
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar {
NSLog(#" searchBarResultsListButtonClicked for %#",searchBar); //
}
Advice, tutorials, sample code and justified dope-slaps welcome.
TIA
-Mike
#Rayfleck, I think you should not worry about Search Results Button at all.
If what you need is to monitor user's input until they have entered enough characters for filtering:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchText length]>5) {
[self filterDataWithKeyword:searchText];
[self.tableView reloadData];
} else {
[self resetFilter];
[self.tableView reloadData];
}
}
Here is a partial answer that you can stick in viewDidLoad. It should hide the clear button, but it doesn't keep the results button visible. I'm not sure how the results button view logic is controlled behind the scenes.
for (id subview in mySearchBar.subviews) {
if ([[subview class] isSubclassOfClass:[UITextField class]]) {
[subview setClearButtonMode:UITextFieldViewModeNever];
break;
}
}
Since this approach uses all public APIs your app shouldn't get rejected. Although this approach might be prone to breaking further down the road if/when Apple decides to change the hierarchy of UISearchBar. All I'm doing is looking for the UITextField or subclass and setting its clearButtonMode.
Hope this helps.

Tracking the firstResponder object

I need to keep track of which text field is the firstResponder for my custom keyboard to work. In the code below, I have grossly oversimplified my program, but here is the gist of the problem:
#implementation SimplePickerViewController
#synthesize pickerKeyboard;
#synthesize textView;
#synthesize textView2;
#synthesize firstResponder;
-(void)viewDidLoad{
pickerKeyboard = [[PickerKeyboardViewController alloc] initWithNibName:#"PickerKeyboard" bundle:nil];
pickerKeyboard.delegate = self;
[self.textView setInputView:pickerKeyboard.view];
[self.textView setDelegate:self];
[self.textView2 setInputView:pickerKeyboard.view];
[self.textView2 setDelegate:self];
}
-(void)hideKeyboard{
[self.firstResponder resignFirstResponder];
self.firstResponder = nil; //without this line, the code doesn't work.
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
self.firstResponder = textView;
[self.pickerKeyboard.picker reloadAllComponents];
return YES;
}
If I remove the line setting the firstResponder to nil, the code ceases to function properly, but I am not sure why. (Without that line, I can select the first textView to bring up the keyboard, but after that I can never bring the keyboard back. Any ideas? Thanks!
I'm not sure that I understand why firstResponder needs to be kept track of for a custom keyboard to work. I use a custom keyboard without knowing what the first responder is.
Do you use:
textView.inputView = pickerKeyboard
How about the following, called on the view to resign the first responder:
[self.view endEditing:NO];
I have had a similar problem and I have just figured out the issue. Somewhere in some part of Apple's first responder code, they are using a selector named firstResponder. When you created the property firstResponder you inadvertently overrode that selector. That will cause Apple's code to fail. This, in my humble opinion, is a bug in Apple's framework, and the firstResponder method isn't documented anywhere. Name your property myFirstResponder or anything else and everything should work just fine.
See Why does the keyboard not show when a view is popped from the navigation stack?

Resources