UITableView scrolling problems when inside a UIScrollView - iphone

I have a UIScrollView (with paging) to which I add three UIViews. Each of these UIViews has a UITableView inside. So, the user should be able to scroll horizontally to the page he wants and then scroll vertically in the corresponding table.
However, some of the tables don't receive the scrolling gestures. Usually the first one does behave good, but the other ones do not. I can't select cells nor scroll the table up or down.
I used the default settings for the UIScrollView, except for these ones defined in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
//Load the view controllers
[self loadViewControllers];
//Configure the scroll view
self.scrollView.pagingEnabled = YES;
self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.scrollView.frame) * viewControllers.count, CGRectGetHeight(self.scrollView.frame));
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.scrollsToTop = NO;
self.scrollView.delegate = self;
//Configure the page control
self.pageControl.numberOfPages = viewControllers.count;
self.pageControl.currentPage = 0;
}
I can't figure out why I can't scroll some of the tables... Any help would be greatly appreciated.
Thanks in advance!

Try to set
self.scrollView.delaysContentTouches = YES;
self.scrollView.canCancelContentTouches = NO;
Maybe the UIScrollView don't pass touch informations to the subviews.

I tried to reproduce a simplified version of your needs using basically Interface Builder and it seems to me it's working using basic coding and using default settings. Can you pls check my quick n dirty Github repo and kindly ask to reply whether it is applicable to your situation or what is missing.
https://github.com/codedad/SO_ScrollView_with_Tables
By default Interface Builder creates UIScrollView and UITableViews enabling:
Delays Content Touches ON
Cancellable Content Touches ON

Things I would check:
Check your View Hierarchies - Is something being laid on top of your UITableView, causing it not to receive a tap?
Are your UITableViews being disabled anywhere? I would set a breakpoint in tableView:didSelectRowAtIndexPath: and see if that method is being called.
Check this post
I guess those aren't sure-fire answers but hopefully they'll help discover the problem!

This worked for me
I programmatically added the tableView to my scroll view using addSubview:
UIGestureRecognizerDelegate is needed.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isDescendantOfView:self.signUpJammerList]) {
return NO;
}
return YES;
}

Related

UIButton interaction inside UIPageViewController

I'm using an UIPageViewController in my application and I wanted to have a few UIButtons inside it, sort of like a menu. The problem I have is that when I put an UIButton (or any other interactive element) near the edges of the screen and tap it, instead of the UIButton action being applied, what happens is that the page changes (because the tap on the edge of the screen changes the page on the UIPageViewController). I'd like to know if there's a way to make it so that the UIButton has higher priority than the UIPageViewController so that when I tap the button, it applies the appropriate action instead of changing the page.
I came here with the same problem. Split’s link has the answer.
Make your root view controller the delegate of each of the UIPageViewController’s gesture recognizers, then prevent touches from being delivered if they occur inside any UIControl:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ([touch.view isKindOfClass:[UIControl class]] == NO);
}
UIPageViewController has two UIGestureRecognizers. You can access them via gestureRecognizers property. Determine which one is UITapGestureRecognizer and then use this. Hope this helps.
For people that just want to copy/paste code, here is mine :
// I don't want the tap on borders to change the page
-(void) desactivatePageChangerGesture {
for (UIGestureRecognizer* gestureRecognizer in self.pageViewController.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
gestureRecognizer.enabled = NO;
}
}
}
Just call this function after the UIPageViewController creation.
I had this same problem, and was unsure how to handle the UIGestureRecognizer delegate methods. This short example assumes you are using the "Page Based Application" project type in Xcode 4. Here is what I did:
In RootViewController.h, I made sure to announce that RootViewController would handle the UIGestureRecognizerDelegate protocol:
#interface RootViewController : UIViewController <UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
In RootViewController.m, I assigned RootViewController as the delegate for the UITapGestureRecognizer. This is done at the end of the viewDidLoad method. I did this by iterating over each gestureRecognizer to see which one was the UITapGestureRecognizer.
NSEnumerator *gestureLoop = [self.view.gestureRecognizers objectEnumerator];
id gestureRecognizer;
while (gestureRecognizer = [gestureLoop nextObject]) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[(UITapGestureRecognizer *)gestureRecognizer setDelegate:self];
}
}
Finally, I added the gestureRecognizer:shouldReceiveTouch method to the bottom of RootViewController.m (This is copied directly from Split's link):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIControl class]]) {
// we touched a button, slider, or other UIControl
return NO; // ignore the touch
}
return YES; // handle the touch
}
Comment out these line from your code
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
or use UIGestureRecognizer as told by Split
Hope this will help you
OLD ANSWER: If your UIPageViewController has a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+, then you can't use the gestureRecognizer:shouldReceiveTouch: method, because there is no way to set the delegate to self on the gestureRecognizers since pageViewController.gestureRecognizers will return nil. See UIPageViewController returns no Gesture Recognizers in iOS 6 for more information about that.
If you simply want to make sure your UIPageViewController passes along button touch events to a UIButton, you can use
for (UIScrollView *view in _pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
view.delaysContentTouches = NO;
}
}
if you have a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+.
See this answer about why delaysContentTouches = NO is needed for some cases of a UIButton in a UIScrollView
UPDATE: After doing a little more research it appears that if your issue is that the UIButton click seems to only be called sometimes, then that is actually probably the desired behavior inside a UIScrollView. A UIScrollView uses the delaysContentTouches property to automatically determine if the user was trying to scroll or trying to press a button inside the scroll view. I would assume it is best to not alter this behavior to default to NO since doing so will result in an inability to scroll if the user's finger is over a button.
None of the solutions here where you intercept the UIPageViewController's tap gesture recognizers worked for me. I'm targeting iOS 8 and 9.
What worked is to override the functions touchesBegan, touchesCancelled, touchesMoved, and touchesEnded in my custom button which is a subclass of UIControl. Then I just manually send the .TouchUpInside control event if the touch began and ended within the frame of my custom button.
I didn't have to do anything special for the containing page view controller, or the view controller that contains the page view controller.
Swift 5 answer here should do the job.
pageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first?.delaysContentTouches = false

How to disable touch input to all views except the top-most view?

I have a view with multiple subviews. When a user taps a subview, the subview expands in size to cover most of the screen, but some of the other subviews are still visible underneath.
I want my app to ignore touches on the other subviews when one of the subviews is "expanded" like this. Is there a simple way to achieve this? I can write code to handle this, but I was hoping there's a simpler built-in way.
Hope this help...
[[yourSuperView subviews]
makeObjectsPerformSelector:#selector(setUserInteractionEnabled:)
withObject:[NSNumber numberWithBool:FALSE]];
which will disable userInteraction of a view's immediate subviews..Then give userInteraction to the only view you wanted
yourTouchableView.setUserInteraction = TRUE;
EDIT:
It seems in iOS disabling userInteraction on a parent view doesn't disable userInteraction on its childs.. So the code above (I mean the one with makeObjectsPerformSelector:)will only work to disable userInteraction of a parent's immediate subviews..
See user madewulf's answer which recursively get all subviews and disable user interaction of all of them. Or if you need to disable userInteraction of this view in many places in the project, You can categorize UIView to add that feature.. Something like this will do..
#interface UIView (UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value;
#end
#implementation UIView(UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value{
self.userInteractionEnabled = value;
for (UIView *view in [self subviews]) {
[view setRecursiveUserInteraction:value];
}
}
#end
Now you can call
[yourSuperView setRecursiveUserInteraction:NO];
Also user #lxt's suggestion of adding an invisible view on top of all view's is one other way of doing it..
There are a couple of ways of doing this. You could iterate through all your other subviews and set userInteractionEnabled = NO, but this is less than ideal if you have lots of other views (you would, after all, have to subsequently renable them all).
The way I do this is to create an invisible UIView that's the size of the entire screen that 'blocks' all the touches from going to the other views. Sometimes this is literally invisible, other times I may set it to black with an alpha value of 0.3 or so.
When you expand your main subview to fill the screen you can add this 'blocking' UIView behind it (using insertSubview: belowSubview:). When you minimize your expanded subview you can remove the invisible UIView from your hierarchy.
So not quite built-in, but I think the simplest approach. Not sure if that was what you were thinking of already, hopefully it was of some help.
Beware of the code given as solution here by Krishnabhadra:
[[yourSuperView subviews]makeObjectsPerformSelector:#selector(setUserInteractionEnabled:) withObject:[NSNumber numberWithBool:FALSE]];
This will not work in all cases because [yourSuperView subviews] only gives the direct subviews of the superview. To make it work, you will have to iterate recursively on all subviews:
-(void) disableRecursivelyAllSubviews:(UIView *) theView
{
theView.userInteractionEnabled = NO;
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
-(void) disableAllSubviewsOf:(UIView *) theView
{
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
Now a call to disableAllSubviewsOf will do what you wanted to do.
If you have a deep stack of views, the solution by lxt is probably better.
I would do this by putting a custom transparent button with the same frame as the superView. And then on top of that button I would put view that should accept user touches.
Button will swallow all touches and views behind it wouldn't receive any touch events, but view on top of the button will receive touches normally.
Something like this:
- (void)disableTouchesOnView:(UIView *)view {
UIButton *ghostButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
[ghostButton setBackgroundColor:[UIColor clearColor]];
ghostButton.tag = 42; // Any random number. Use #define to avoid putting numbers in code.
[view addSubview:ghostButton];
}
And a method for enabling the parentView.
- (void)enableTouchesOnView:(UIView *)view {
[[view viewWithTag:42] removeFromSuperview];
}
So, to disable all views in the parentViev behind yourView, I would do this:
YourView *yourView = [[YourView alloc] initWithCustomInitializer];
// It is important to disable touches on the parent view before adding the top most view.
[self disableTouchesOnView:parentView];
[parentView addSubview:yourView];
Just parentView.UserInteractionEnabled = NO will do the work.
Parent view will disable user interaction on all the view's subviews. But enable it does not enable all subviews(by default UIImageView is not interactable). So an easy way is find the parent view and use the code above, and there is no need to iterate all subviews to perform a selector.
Add a TapGestureRecognizer to your "background view" (the translucent one which "grays out" your normal interface) and set it to "Cancels Touches In View", without adding an action.
let captureTaps = UITapGestureRecognizer()
captureTaps.cancelsTouchesInView = true
dimmedOverlay?.addGestureRecognizer(captureTaps)
I will give my 2 cents to this problem.
Iteratively run userInteractionEnabled = false it's one way.
Another way will be add a UIView like following.
EZEventEater.h
#import <UIKit/UIKit.h>
#interface EZEventEater : UIView
#end
EZEventEater.m
#import "EZEventEater.h"
#implementation EZEventEater
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = false;
}
return self;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//EZDEBUG(#"eater touched");
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
In your code you add the EZEventEater view to cover all the views that your may block the touch event.
Whenever you want to block the touch event to those views, simply call
eater.userInteractionEnabled = YES;
Hope this helpful.
In Swift 5, I achieved this behaviour by placing a view right on top(the highlighted one) and setting:
myView.isUserInteractionEnabled = true
This does not let the touches go through it, thus ignoring the taps.
For my app, I think it will be sufficient to disable navigation to other tabs of the app (for a limited duration, while I'm doing some processing):
self.tabBarController.view.userInteractionEnabled = NO;
Also, I disabled the current view controller--
self.view.userInteractionEnabled = NO;
(And, by the way, the recursive solutions proposed here had odd effects in my app. The disable seems to work fine, but the re-enable has odd effects-- some of the UI was not renabled).
Simple solution. Add a dummy gesture that does nothing. Make it reusable by adding it to an extension like this:
extension UIView {
func addNullGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(nullGesture))
addGestureRecognizer(gesture)
}
#objc private func nullGesture() {}
}
setUserInteractionEnabled = NO on the view you want to disable
I had the same problem, but the above solutions did not help.
I then noticed that calling
super.touchesBegan(...) was the problem.
After removing this the event was only handled by the top-most view.
I hope this is of help to anybody.

How do I scroll a set of UIScrollViews together?

Setup: I have a UITableView, each UITableViewCell has a UIScrollView. What I am trying to do is to get all of the UIScrollViews to scroll together, such that when you scroll one of them all of the UIScrollViews appear to scroll simultaneously.
What I've done is subclass UITableView so that it has an array of all of the UIScrollViews within its table cells. I then forwarded TouchesBegan, TouchesMoved, TouchesCancelled, and TouchesEnded from the UITableView to all of the UIScrollViews in the array.
This doesn't appear to work. The UIScrollViews do not scroll! The only way I've managed to get this to work is to call the setContentOffset: method on the scrollviews. However, this is a pretty bad solution since it doesn't give you the swiping and deceleration features of the UIScrollView.
Any ideas on why my touches methods aren't getting to the UIScrollViews? Or a better way to implement this?
Ok, got it working. Thanks for the tips Ricki!
2 things to add to Ricki's solution, if you want to avoid an infinite loop, you have to check to see whether the scrollView's tracking or dragged properties are set. This will insure that only the ScrollView that is actually being dragged is calling the delegate.
- (void)scrollViewDidScroll:(UIScrollView *) theScrollView {
if (theScrollView.dragging || theScrollView.tracking)
[self.delegate scrolling:[theScrollView contentOffSet]];
}
Also, in the scrolling method of the delegate, I set animated to NO, this got rid of the delay between the initial swipe and the other scrollviews getting updated.
I did something "similar" where I had 4 scrollViews incased inside a parent view.
I placed a scrollView inside a UIView, this UIView was passed a delegate from its parentView, that was the view who kept track of all the scrollViews. The UIView containing a scrollVIew implemented the UIScrollViewDelegate and this method;
- (void)scrollViewDidScroll:(UIScrollView *) theScrollView {
[self.delegate scrolling:[self.scrollView contentOffSet]];
}
Now the parent view did this on all the scrollViews:
- (void) scrolling:(CGFloat) offset {
for(UIScrollView *s in self) {
[s setContentOffset:offset animated:YES];
}
}
It is of course a bit of a strain on the CPU, but scrolling several views will be that under any circumstances :/
Hope this was something in the direction of what you needed, and that it made any sense.
Added:
I took me 8 different paths and a lot of mass chaos before I made it work. I dropped the touchedBegan approach early, there is just no way to write something that comes close to Apples swipe, flick, scroll algorithms.
I don't know if the tableview and scrollview will "steal" each others touch events, but as I can read from your description you made that part work.
A follow up idea to ease the CPU usage. add each scrollview to a cell, set its tag=14, now when scrolling asked for all visible cells only, ask for viewWithTag=14, set the contentOffset on this. Save the content offset globally so you can assign it to cells being scrolled onto the screen in cellForRowAtIndexPath.
So set the offSet to a global property, in cellForRowAtIndexPath find the view with tag = 14, set its offset. This way you don't even need a reference to the scrollViews only the delegate.
If you have differently sized UIScrollViews and are using paging, this works great:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView {
#pragma unused(_scrollView)
categoryPageControlIsChangingPage = NO;
for (UIImageView *iv in [categoryScrollView subviews]) {
iv.alpha = (iv.tag != categoryPageControl.currentPage+1)?0.5f:1.0f;
ILogPlus(#"%i %i", iv.tag, categoryPageControl.currentPage+1);
}
[self scrolling:_scrollView];
}
- (void)scrolling:(UIScrollView *)sv {
CGFloat offsetX = sv.contentOffset.x;
CGFloat ratio = offsetX/sv.contentSize.width;
if ([sv isEqual:categoryScrollView]) {
[categoryScrollViewLarge setContentOffset:CGPointMake(ratio*categoryScrollViewLarge.contentSize.width, 0) animated:YES];
}else {
[categoryScrollView setContentOffset:CGPointMake(ratio*categoryScrollView.contentSize.width, 0) animated:YES];
}
}

Properly zooming a UIScrollView that contains many subviews

I created a zoomable UIScrollView and added 100 subviews to it (tiled). The view scrolls perfectly left and right. However, I'd like to allow zooming.
To do so I read that my delegate needs to implement:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return ???;
}
I have seen examples that have only one subview to zoom, so they return that subview in that method. In my case, however, I have a lot more. What is the proper way to do the zooming?
I tried creating another UIView and adding the 100 subviews to that one. And then return that one view on the method above, but I doesn't work (it zooms but once it stops, it's not interactive any more).
Exactly,
This is what Apple also is mentioning in the Scroll View Programming Guide:
Just create a view, add all subviews to this view and add the newly created view as a single subview to the scrollview...
Then in the viewForZoomingInScrollView delegate method return the object at Index 0:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return [self.scrollView.subviews objectAtIndex:0];
}
I created the view where I added everything using:
UIView *zoomableView = [[UIView alloc] init];
without setting its frame.
The problem was solved when, after adding all the subviews to it, I set its frame to something large enough to accommodate all the tiled subviews.
You have to return what your going to add views to scroll view as a subviews.
Ex: If you are adding image view to scroll view then write
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return imageView object;
}

Changing the size of the UISearchBar TextField?

I have a UITableView with an Index on the side; I want to add a UISearchBar to it, but the index overlaps with the "x" to clear the search. I've noticed in the Contacts application, the textfield within the UISearchBar is resized to accommodate this, but I can't work out how to do this in my own app.
I have tried the following in my viewDidLoad, but it does not seem to work.
UITextField * textField = (UITextField *)[[self.search subviews] objectAtIndex:0];
CGRect r = textField.frame;
[textField setFrame:CGRectMake(r.origin.x, r.origin.y, r.size.height, r.size.width-30)];
Any ideas?
it's much easier than all these suggestions. In interface builder, instead of putting the Search Bar as the header of your Table View, you can put a View instead. Then, put a Navigation Bar inside this View. Grab the left resizing handle of the Navigation Bar and pull it to the right until the N B is only 25 pixels wide. Clear out the Title in the N B (double click to select it, then delete). Then, add a Search Bar into the same View. Move its right resizing handle to the left, adjust so that it abuts the N B. That's it.
You can enable a cancel button if you want too and it also won't overlap the index (remains within the search bar).
Apparently a Table View can only have 1 subview in its header, that's why you need to put the View first, then the N B and Search Bar inside it.
UPDATE: see Beginning iPhone Development from Apress, p. 241 of SDK 3 edition. You just disable the index while searching.
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
if (isSearching) {
return nil;
}
return keys;
}
Also they talk about adding a magnifying glass to the top of the index.
Great book all around.
Why not just make the actual UISearchBar smaller horizontally, and place an (empty) UINavigationBar to the right of it? They will render the exact same background.
Better than hacking the internals of Apple's objects that could change.
Also, when animating the UISearchBar's width, you'll notice that the inner text field is not animated along with it. You can fix this by calling UISearchBar's "layoutSubviews" within your animation block after changing its frame. (that's where it determines the size of the inner text field)
Ok, I've come up with a solution.
Create a subclass of UISearchBar
Include this code in the drawRect: method.
UITextView * textField = [self.subviews objectAtIndex:0];
textField.frame = CGRectMake(5, 6, (310 - kRightSideMargin), 31);
[super drawRect:rect];
Note: kRightSideMargin is a constant I set in my header file; I have it set to 25.
Thanks for the suggestions from everyone else.
As Padraig pointed out all you have to do is subclass out the searchBar. Create your UISearchBar subclass, and add the following code into the layoutSubviews method:
- (void)layoutSubviews
{
UITextField *searchField;
for(int i = 0; i < [self.subviews count]; i++)
{
if([[self.subviews objectAtIndex:i] isKindOfClass:[UITextField class]])
{
searchField = [self.subviews objectAtIndex:i];
}
}
if(!(searchField == nil))
{
searchField.frame = CGRectMake(4, 5, 285, 30);
}
}
This loops through all the subviews and checks them against type UITextField. That way if it ever moves in its line up of subviews this will still grab it. I found 285 to just wide enough not to overlap with the index of my tableView.
As of iOS 6, the navigation bar solution didn't work well for me because of slightly different looks now between the UISearchBar and UINavigationBar. So, I switched to something similar to Padraig's approach by subclassing the UISearchBar.
#interface SearchBarWithPad : UISearchBar
#end
#implementation SearchBarWithPad
- (void) layoutSubviews {
[super layoutSubviews];
NSInteger pad = 50;
for (UIView *view in self.subviews) {
if ([view isKindOfClass: [UITextField class]])
view.frame = CGRectMake (view.frame.origin.x, view.frame.origin.y, view.frame.size.width - pad, view.frame.size.height);
}
}
#end
Edit: Ah, I haven't tried it, but I think you might be able to set a navigation bar's clipToBounds = YES to turn off it's new shadow, thereby creating a consistent look again between the two controls.
I am using ViewDeck and want to show a UISearchbar inside the leftController.
Now the problem is if I open the left side which contains the navigation, the right bit overlaps my search field.
I got rid of this by over writing UISearchBar, the textfield will always have the same width, but in one case there is the ViewDeck overlapping and in the other case I hide the ViewDeck-bit and then the cancel button will take up the space:
Subclassing UISearchBar
#import "ViewDeckSearchBar.h"
#define kViewDeckPadding 55
#interface ViewDeckSearchBar()
#property (readonly) UITextField *textField;
#end
#implementation ViewDeckSearchBar
static CGRect initialTextFieldFrame;
- (void) layoutSubviews {
[super layoutSubviews];
// Store the initial frame for the the text field
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initialTextFieldFrame = self.textField.frame;
});
[self updateTextFieldFrame];
}
-(void)updateTextFieldFrame{
int width = initialTextFieldFrame.size.width - (kViewDeckPadding + 6);
CGRect newFrame = CGRectMake (self.textField.frame.origin.x,
self.textField.frame.origin.y,
width,
self.textField.frame.size.height);
self.textField.frame = newFrame;
}
-(UITextField *)textField{
for (UIView *view in self.subviews) {
if ([view isKindOfClass: [UITextField class]]){
return (UITextField *)view;
}
}
return nil;
}
#end
ViewController class
In my Navigation class I need to overwrite these two UISearchbarDelegate methods in order to go to fullscreen with the search results:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar{
[self.viewDeckController setLeftSize:0];
// I am also using scopes, which works fine (they fade out when not searching)
self.searchBar.scopeButtonTitles = #[#"Food",
#"Beverages",
#"Misc"];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar{
self.viewDeckController.leftSize = 55;
}
Result
ViewDeck showing to the right:
(source: minus.com)
Search in Fullscreen (The button and the scope buttons are animated in).
(source: minus.com)
searchBar.layoutMargins = UIEdgeInsetsMake(0, 0, 0, rightPad);
My old solution of changing the UITextField frame stopped working in iOS 13. Putting a UINavigationBar to the right of the UISearchBar never worked well for me as they had different looks at top and bottom.
Sorry to drag this all up again.
I wanted the UISearchBar to be shorter, and I'm using a UISearchBarController, but without actually wanting the index. This is because I have an overlay to the right:
To do this, I fake a sectionIndex with one blank item, then hide it. Here's how I do that:
- (void)hideTableIndex {
for (UIView *view in [tableView subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UITableViewIndex")]) {
view.hidden = YES;
}
}
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView {
if (aTableView == self.searchDisplayController.searchResultsTableView) {
return nil;
} else {
[self performSelector:#selector(hideTableIndex) withObject:nil afterDelay:0];
return [NSArray arrayWithObjects:#"", nil];
}
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return 0;
}
This shortens the the UISearchBar and hides the index so it can't be tapped (a small section would otherwise hand to the left of the overlay that when tapped would scroll the UITableView to the top). Like this:
Best of all, when you use the search, you still get the full width bar:
Just put a UIView and put the search bar inside that UIView. UIView must be of same size as UISearchBar.
this worked for me.
The text field used in UISearchBar is a subclass of UITextField called UISearchBarTextField.
AFAIK, there's no way to resize a UISearchBarTextField using the public API, and the private API doesn't reveal much either.
Maybe you can take a look at UISearchBarTextField's subviews, if it has any.
UPDATE: It doesn't.
UPDATE 2: I think you should take a look at UITextField's rightView property. The below code, although it doesn't work, seems like a good starting point:
UIView *emptyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[textField setRightView:emptyView];
[textField setRightViewMode:UITextFieldViewModeAlways];
[emptyView release];
Sorry for Necroposting, but I found another way to make a little space on the right of the textfield.
I was having the problem, that I had an indexed tableview with a searchbar as the first row. Now the index and the searchbar (made in IB, btw.) were overlapping. It tried almost everything with no success. It seems that the width and height properties of the textifield don't respond... So I came up with this:
searchBar.showsCancelButton = YES;
UIView *cButton = [searchBar.subviews objectAtIndex:2];
cButton.hidden = YES;
I still can't adjust the size of the space, but this does it for now... although... pretty weird solution...
Everyone has provided ways to modify the UI. I have discovered how to obtain identical results. You must provide the following two implementations:
Use UISearchDisplayController
More importantly, make sure you initialize it with:
- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController
Failure to set a valid UISearchBar (or passing nil) will prevent the adjustment of the UITextField for the index.
You must return a valid array of titles by implementing:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;
If you return nil, the index will not be displayed, and the UITextField will not be properly adjusted.
I've submitted a bug report to Apple, suggesting that it seems logical that only #2 should be required, not #1. I have found nothing in the Human Interface Guideline (iPhone HIG) requiring use of the UISearchDisplayController.
The key is to use the "Search Bar and Search Display Controller" and not the "Search Bar" when using Interface Builder.
It kind of looks as though Apple resize the view (note that the index is animated to the right, off screen), making it bigger than the screen.
I would imagine that you'd need to implement the searchBarTextDidBeginEditing: method of the UISearchBarDelegate to trigger this at the appropriate point. This does, however, feel a bit hacky do maybe there's a better way of doing it.
Another appraoch (though tedious) would be to resize the search bar and fill the 'gap' with a navigation bar. Works for me.
What I've come up with isn't too much better. Basically, I make an empty view with the frame that I want to use for the search bar. Then I create a UIToolbar to go behind the search bar. Be sure to set its frame to the same frame as the UIView, except that the Y value has to be -1; otherwise, you'll get two borders drawn at the top. Next create your UISearchBar, but set the frame's width to 30 (or whatever makes sense for your app) less than the UIView. Add them as subviews and set your UIView as the tableHeaderView.
I followed Mike's advice by making a UIView, then putting a Navigation Bar and UISearch Bar inside it. Only problem is first time the search bar is shown its background is the same as a Navigation Bar normally?
Interestingly, if I activate the search, then click cancel the background of this 'fixed'!?
I'm using SDK 3.0, so I removed the UISearchBar item made when I dragged a UISearchDisplayController in to my NIB, then made the view as described above and wired it up to the file owner and the searchBar outlet in the search display controller.
It work fine!!!
[searchBar setContentInset:UIEdgeInsetsMake(5, 0, 5, 35)];