iOS: Scope bar hidden when search bar is displayed in navigation bar - ios

I'm trying to get a search bar and it's scope in the navigation bar, as the way the view first appears. If you simply drag a UISearchBar onto you table view controller in interface builder, it's placed in your table like a header cell. Then, when you tap it, it animates into the formation I'm after below. The problem is I want it to start out this way without any tapping, with a back button on the left and no cancel button on the right:
So, to give the search bar immediate focus without tapping, I tried adding [_searchBar becomeFirstResponder]; in viewDidLoad which doesn't work. Next I tried:
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
The search bar will indeed display in the navigation bar but the scope bar is gone, (WHY APPLE WHY) and there is an awkward gap.
I've also tried subclassing UINavigationBar and making it taller. It's easy to change it's size but the contents align at the bottom of the bar and overlap anything you try to add to it.
So to re-iterate, I need the search bar to display in the navigation bar with a scope control when the view first appears, without any tapping by the user. I should also specify that this is on a UITableViewController (because the page has a UIRefreshConrol), so simply dropping a toolbar in above the table view isn't an option. Thanks.

tl;dr: Don't.
You should know that adding a UISearchBar to the headerView of a UITableView invokes some custom code within UIKit that facilitates hiding the scope bar on start. While it used to be easier to show the scope bar all the time in iOS 6 and older, iOS 7 has changed this.
Your first approach, to becomeFirstResponder in viewDidLoad was a good idea, but this method is called after the view has been loaded into memory (such as out of a NIB). The view has not yet been added to the view hierarchy, so it can't become the first responder. viewDidAppear: is called right after the view is loaded into the view hierarchy, and a becomeFirstResponder does in fact allow the UISearchBar to receive focus.
The scope bar itself is a hidden (by default) subview of UISearchBar. You technically could just cycle through subviews and set it to not be hidden:
- (void)viewDidLoad
{
[super viewDidLoad];
[self recurseSubviewsForView:self.searchDisplayController.searchBar];
}
- (void)recurseSubviewsForView:(UIView *)view
{
for (UIView *subview in view.subviews) {
if ((CGRectGetMinY(subview.frame) == CGRectGetMaxY(self.searchDisplayController.searchBar.frame)) && subview.hidden) {
subview.hidden = NO;
self.searchDisplayController.searchBar.showsScopeBar = YES;
[self.searchDisplayController.searchBar sizeToFit];
}
[self recurseSubviewsForView:subview];
}
}
This is generally a bad idea. There's a basic check to see that the subview being targeted is the subview containing the scope bar, but that could break any second, might not be backwards compatible, etc.
The real question you have to ask yourself is why do I want the scope bar to always be visible? The way it works now, the scope bar will animate visible when the user taps into the search field and hide itself when the search field no longer has the focus. Even with the "hack" above, the search bar gets the focus as soon as the user taps a scope button. What's the point of showing a search option when search isn't even active? When in doubt, fall back to Apple's suggestions. It will make your life a lot easier in the long run, and probably make your app look and behave better. Apple didn't just add that interaction on a whim. They tested it like crazy. That's why it's so hard to make it work another way. That's why there's special code run when a UISearchBar is the headerView of a UITableView...because it's likely better for the user.
In addition, I see your screenshot targeting iOS 8, but your code snipped shows using a UISearchDisplayController. This paradigm has been deprecated in favor of UISearchController. Please consider updating.

Related

Full-window view in front of a collection view

I'm trying to implement the following behavior:
Long press on a collection view brings a full-window view (call it LetterView) to the front
Subsequent gestures/touches are only processed by the LetterView.
(edit: I should mention that I want a transparency effect of seeing the collectionview items beneath LetterView)
I seem to be running into behavior that everyone else is trying to implement, though - my touches get processed by both the LetterView and the collection view. I.e. I can scroll the collection view AND have hits processed by my topmost view. Showing the view hierarchy in XCode clearly shows LetterView at the front, and both the UICollectionView and the LetterView are subviews of UICollectionWrapperView.
LetterView is a UIView subclass with a UIViewController subclass. It's added to the view hierarchy programmatically, inside my UICollectionViewController subclasses's viewDidLoad method, like so:
super.viewDidLoad()
letterDrawingViewController = LetterDrawingViewController()
let viewFrame : CGRect = self.collectionView!.frame
letterDrawingViewController.view = LetterDrawingView.init(frame:viewFrame)
letterDrawingView = letterDrawingViewController.view
self.addChildViewController(letterDrawingViewController)
letterDrawingViewController.didMoveToParentViewController(self)
collectionView?.addSubview(letterDrawingView)
It doesn't appear to be a first responder issue, as I tried overriding canBecomeFirstResponder in LetterView and assigning it first responder status when I move it to the front
I tried setting userInteractionEnabled=FALSE on the CollectionView, but keeping it true on the LetterView after I moved LetterView to the front. This disabled all touch events for both views
I tried setting exclusiveTouch=True for LetterView when I moved it to the front. This didn't appear to do anything.
Aside from any specific tips, are there any general techniques for debugging hit-testing like this? According to the docs on hit-testing in iOS, iOS should prefer the "deepest" subview that returns yes for hitTest:withEvent:, which, since LetterView is a subview of collectionview, and in front of all it's cells, should be the front? Is there any logging I can enable to see a hit test over the view hierarchy in action?
Thank you!
Nate.
If letterView is full screen, you probably don't want to add it as a subview of the collection view like you are. Maybe try adding it to the application's window instead and see how that does. At least in that instance it should intercept all the touch events.
Another method, although admittedly a more fragile feeling one, would be to enable and disable user interaction on the collectionView as you present and dismiss letterView.
So, when letterView is about to be presented, you can call
self.collectionView.userInteractionEnabled = NO;
and if you also know when that view is about to be dismissed you can call
self.collectionView.userInteractionEnabled = YES;
The only thing here to worry about is that you don't get into a bad state where your letterView is not presenting and your collectionView is also ignoring a user's touch. That will feel totally broken.
Whilst I think you can deal with your issue somewhat easily I think you are making a design mistake. It feels like you are trying to code this thinking like a web developper by adding a child view to your view and trying to intercept the touches there like one would do in a modern JavaScript single page app. In iOS I think this is bad design. You should segue or present the new viewController using the methods provided by apple.
So your code should look soothing like:
letterDrawingViewController = LetterDrawingViewController()
self.presentViewController(letterDrawingViewController, animated: true, completion: nil)
iOS8 has the added benefit of allowing you to have awesome custom transitions. Take a look at this : http://www.appcoda.com/custom-segue-animations/

Switching between UIViewControllers with UISegmentedControl

Right I have looked at a few SO questions on the subject and I am finding it difficult to come up with the correct solution here.
Requirements
I have a UITabBar based application. One of the tabs has a UINavigation controller with UISegmentedControl at the top allowing the user to switch between three different views.
Each view will have a UITableView which will allow the user to navigate to another view. These views should be pushed onto to the navigation controller.
Problem
Now all the SO questions and Answers on the subject show how to switch between views. However I need the view I switch to, to allow pushing of another view onto the navigation stack. I am not sure this is even possible. I thought about UIViewController containment - however that would show a view being pushed onto the stack in a smaller window that the screen's bounds. Not what I am looking for.
Any ideas how I can solve this with storyboards and UIViewControllers?
UPDATE
Here is what I am trying to do: In the screenshot the container area is where I need to load other view controllers into. The UISegment control cannot go into the navigation bar as that space is used for something else. So that's why I think UIViewController containment might be better here?
So even though this isn't using separate TableViewControllers, you can use different custom UIViews that are hidden by default and become visible when you select it's corresponding button. This will unfortunately make it so you have all three view's logic in the same VC.
To get around this, you can try setting up some delegates and mimicking the TableViewController logic separation by sending out the didSelectTableAtIndexPath, UIGesture touches, etc into classes outside the ViewController to help keep your code cleaner.
User UITabBarController and hide the tab bar.
- (void)viewDidLoad
{
self.tabBar.hidden = YES;
}
Binding the segment control with method valueChanged
- (void)valueChanged:(UISegmentedControl *)seg
{
if ([seg.selectedSegmentIndex == 0]) {
self.selectedIndex = 0;
} else if ([seg.selectedSegmentIndex == 1] {
self.selectedIndex = 1;
}
}
I achieve this by this way, I hope this will help.

IOS7: Pop ViewController forces the UIImageView to drop

After upgrading my project to iOS7
when I do a BACK Button and the UINavigationController goes back to the previous page, an ImageView on the top of the screen shifts down.
I use IB to do my layouts. These are my Simulated Metrics:
I have AutoLayout off. Any ideas on what the issue might be? I wasnt sure if anyone wants to see specific code and I didnt want to clutter up the question with too much code.
Updates: Based on the comment questions, I wanted to make these updates:
In no place in the application .h or .m file do I make any changes to the imageview's sizes or location.
In both the viewDidLoad and viewDidAppear I call a user-defined method called recalculateAll but their is no reference at all to any imageview sizes. Just for trying it out I commented out the entire section and ran the code and it still jumps down.
In my init I do programatically set some imageviews (you see the #132 in what appears to be a bubble) using their x and y's.
Here is a typical navigation I use for moving from the view controller to the tableviewcontroller:
GetTimeOffByType *showTimeOffReport = [[GetTimeOffByType alloc] initWithNibName:#"GetTimeOffByType" bundle:nil];
showTimeOffReport.timeOffType = #"Vacation";
[self.navigationController pushViewController:showTimeOffReport animated:YES];
These are all .xib files, no storyboarding at all. Its basically a view controller which has an embedded UINavigationController with 6 buttons. Each time a button is pressed it pushes a UITableViewController passing different parameters and showing different data. The transition I am using to get back to the original UIViewController is simply the iOS generated BACK button (so no code to show for that)
Update#2 Hopefully this will help someone solve this wierd behavior. So if I were to click on the table view cell on showTimeOffReport to get the cell detail and then using BACK navigate all the way back it doesnt jump down.
Update#3 Ok this is something I just discovered : The issue of jumping down or not is related to the translucency of the UINavigationBar. If you have a Translucent = YES it will start from the top of the window. If you have a translucent = NO it will start from the bottom of the UINavigationBar.
You might try setting the new property on UIViewController edgesForExtendedLayout to UIRectEdgeNone.
Here is a good resource that explains more about how view layouts changed in iOS 7.
See Apple Documentation
If you plan to be backwards compatible you will probably need to do some runtime checks and adjust positioning if the device is not running iOS 7.
This might help you..You can try adding UIViewControllerBasedStatusBarAppearance key and set it's value NO in your info.plist
UIViewControllerBasedStatusBarAppearance = NO

UIBarButtonItem not disabled until view changes

I have a UINavigationController and am using the toolbar in one of my view controllers. I have several UIBarButtonItems. At various points, I disable certain buttons in the toolbar, using things like _btnEdit.enabled = NO.
This all works well except for one time where this happens when there is no user interaction. In that case, the button appears to be enabled (isn't grayed out), but doesn't accept touches. If I cover the bar with something (an action sheet from the bottom) or change the orientation of the device, it shows correctly.
I've tried self.navigationController.toolbar setNeedsDisplay] and [self.navigationController.toolbar drawRect:self.navigationController.toolbar.bounds] but neither have an any effect.
Any ideas on how to "refresh" this view? I know UIBarButtonItems don't inherit from UIView, which I feel like may be contributing to the issue.
This is the intended behaviour. The setNeedsDisplay is a good reflex try, but you don't own the navigation bar and UIBarButtonItem doesn't inherit from UIView, so we need to think of them a little differently.
Here how you can achieve your goal :
UIBarButtonItem *bbi = self.navigationItem.rightBarButtonItem;
bbi.enabled = NO;
[self.navigationItem setRightBarButtonItem:bbi animated:YES];
NOTE : self is a UIViewController
I've just made a quick test with this and it's working.

Toolbar disappears when hiding keyboard

I have a nav controller with a toolbar. I made the toolbar also appear on top of the keyboard when the keyboard appears. When I dismiss the keyboard, the toolbar disappears, leaving a black rectangle at the bottom of the screen, just where the toolbar should be without the keyboard.
Here's how I init the toolbar:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setToolbarHidden:NO];
// this makes sure the toolbar appears on top of the keyboard
// instead of going below it.
// _nameText is a UITextField
_nameText.inputAccessoryView = self.navigationController.toolbar;
}
This is how I hide the keyboard:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
[self.navigationController setToolbarHidden:NO]; // this doesn't help
return NO;
}
I tried also doing [self.view setNeedsLayout], but that didn't work.
EDIT: I suspect this may have to do with the fact that I assign the toolbar to be the input accessory view of my text field. I think that the text field hides its accessory view when the keyboard goes away. I still don't know how to override that behavior though.
EDIT 2: I discovered that self.navigationController.toolbar.superview is nil after the keyboard is gone.
OK, so while I couldn't solve the problem head-on, I found an acceptable workaround.
Create a .xib for your toolbar
Load the toolbar from (1) into an object
assign that object to the inputAccessoryView property of your text field
set up the target and the actions for the buttons in this toolbar, so you can respond to clicks
You are now all set. Your original toolbar (which you presumably have created in the Interface Builder) is only visible when the keyboard is hidden. When the keyboard is visible, the original toolbar cannot be seen, but your other one (created with the steps described above) now appears above the keyboard. Bingo!
If anyone has a more elegant solution to this problem, I'd be happy to hear about it :)
#BlackRider, I ran into the same exact issue as you. It is quite annoying.
I didn't want to set up 2 different toolbars as a workaround, as I didn't want to handle the state of the toolbar buttons in 2 different places.
I've resorted to using the approach discussed here in the answer that uses notifications: iPhone: How to fix inputAccessoryView to View?
It's working ok - my gut reaction is that I'll run into issues when trying on various device sizes / orientations.

Resources