UISearchBar ignores programmatic constraints - ios

I'm trying to build a ViewController subclass that leverages a UISearchController to display results. The view has an input field that I want to animate up to the top of the screen so the user has a maximum amount of room to view the results.
I built out a proof of concept using a UIView that wraps a Search Bar drop-in component and set their top, leading, trailing, and bottom constraints equal in a Storyboard. The following code is responsible for animating the different movements:
- (void)keyboardWillAppear:(NSNotification*)notification {
self.labelToSearchBarSpaceConstraint.active = NO;
self.searchBarAtTopConstraint.active = YES;
self.searchBarLeadingSpaceConstraint.constant = 0;
self.searchBarTrailingSpaceConstraint.constant = 0;
[UIView animateWithDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^{
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
}
- (void)keyboardWillDisappear:(NSNotification*)notification {
self.searchBarAtTopConstraint.active = NO;
self.labelToSearchBarSpaceConstraint.active = YES;
self.searchBarLeadingSpaceConstraint.constant = 20;
self.searchBarTrailingSpaceConstraint.constant = 20;
[UIView animateWithDuration: [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^{
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
}
I then tried switching over to a programatically created UISearchController and UISearchBar. The following code sets up what I thought was the equivalent relationship in code instead of in the Storyboard:
self.definesPresentationContext = YES;
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
UISearchBar* searchBar = self.searchController.searchBar;
searchBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.searchBarView addSubview:searchBar];
[NSLayoutConstraint constraintWithItem:searchBar attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.searchBarView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0].active = YES;
[NSLayoutConstraint constraintWithItem:searchBar attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.searchBarView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0].active = YES;
[NSLayoutConstraint constraintWithItem:searchBar attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.searchBarView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0].active = YES;
[NSLayoutConstraint constraintWithItem:searchBar attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.searchBarView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0].active = YES;
However, when done this way the UISearchBar object does not obey the constraints. When the container is animated up, the search bar flies off the page. Why is this happening?
Minor Update: I noticed in the documentation for NSLayoutConstraint to set its active property to YES instead of explicitly adding it, but the behavior is exactly the same. Updating code to match.

I had a similar issue when adding UISearchController's searchBar to a UITableView header view.
When animating constraints of the containerView holding the UITableView after the user selected the searchBar, it would not follow the containerView.
In my case it would appear that after the delegate didPresentSearchController:(UISearchController *)searchController is called, the searchBar had a superview of UISearchBarContainerView instead of the UITableView header view.
Re-adding it back to the original header view before animation helped me.
-(void)didPresentSearchController:(UISearchController *)searchController {
NSLog(#"did present search controller");
self.tableView.tableHeaderView = self.searchController.searchBar;
...animation...
}

Related

Adding search bar of UISearchController as a subview of a view using auto layout

I have a view controller class, in whose xib I have added 3 entities: container view, a search bar container view and a button. I have arranged the search bar container and the button at the top of the controller's view using auto layout constraints on the storyboard. Together they span the entire width of the view and take up a height equal to 44px. Below them lies the container view whose top is aligned with the bottom of the search bar container view. Following is the viewDidLoad and other related methods:
- (void)viewDidLoad
{
[super viewDidLoad];
[self addTableView];
self.searchControllerInternal = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchControllerInternal.delegate = self;
self.searchControllerInternal.searchResultsUpdater = self;
self.searchControllerInternal.dimsBackgroundDuringPresentation = NO;
self.searchControllerInternal.searchBar.delegate = self;
[self addSearchControllerSearchBarToView];
}
-(void)addSearchControllerSearchBarToView
{
UISearchBar *pSearchBar = self.searchControllerInternal.searchBar;
self.searchBarContainerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.searchBarContainerView addSubview:pSearchBar];
pSearchBar.translatesAutoresizingMaskIntoConstraints = NO;
[self addSearchBarConstraints];
}
-(void)addSearchBarConstraints
{
UISearchBar *pSearchBar = self.searchControllerInternal.searchBar;
{
NSLayoutConstraint *pConstraint = [NSLayoutConstraint constraintWithItem:pSearchBar attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual toItem:self.searchBarContainerView
attribute:NSLayoutAttributeTop
multiplier:1
constant:0];
[self.searchBarContainerView addConstraint:pConstraint];
self.searchBarTopConstraint = pConstraint;
}
{
NSLayoutConstraint *pLeadingConstraint = [NSLayoutConstraint constraintWithItem:pSearchBar
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.searchBarContainerView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0];
[self.searchBarContainerView addConstraint:pLeadingConstraint];
}
{
NSLayoutConstraint *pTrailingConstraint = [NSLayoutConstraint constraintWithItem:pSearchBar
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.searchBarContainerView
attribute:NSLayoutAttributeRight
multiplier:1
constant:0];
[self.searchBarContainerView addConstraint:pTrailingConstraint];
}
{
NSLayoutConstraint *pBottomConstraint = [NSLayoutConstraint constraintWithItem:pSearchBar
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.searchBarContainerView
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0];
[self.searchBarContainerView addConstraint:pBottomConstraint];
}
}
The view loads properly. However, on tapping the search bar, the search bar moves out of its container view. The search bar container view lies just below the status bar, where the search bar should also be, but it has actually moved out of the visible area. Why is this happening? Also, if the dimsBackgroundDuringPresentation is set to TRUE, the app crashes if user taps anywhere on the screen while search is active, saying that "The view hierarchy is not prepared for the constraint"

iOS - navigation bar displayed when UISearchController is active

I programatically created a UISearchController and added it to my UIView A. I also have a UITableView in the same UIView. I use UISearchController to search through my UITableView. When you click on a cell, UIView B gets pushed. Both UIView A and UIView B have UINavigationBar set to HIDDEN.
When I simply click on a UITableViewCell WITHOUT searching, everything happens perfectly, and UIView B is displayed without UINavigationBar.
But when I am searching using the UISearchController and I click on a UITableViewCell, the UINavigationBar is displayed on UIView B, even though I set it to hidden in the viewDidAppear method.
Here is my code:
BarsSearchController = [[UISearchController alloc] initWithSearchResultsController:nil];
BarsSearchController.searchResultsUpdater = self;
BarsSearchController.dimsBackgroundDuringPresentation = NO;
BarsSearchController.searchBar.delegate = self;
BarsSearchController.searchBar.barTintColor = [UIColor whiteColor];
BarsSearchController.searchBar.searchBarStyle = UISearchBarStyleMinimal;
[searchBarView addSubview:BarsSearchController.searchBar];
[searchBarView addConstraint:[NSLayoutConstraint constraintWithItem:BarsSearchController.searchBar
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:searchBarView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[searchBarView addConstraint:[NSLayoutConstraint constraintWithItem:BarsSearchController.searchBar
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:searchBarView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0]];
[searchBarView addConstraint:[NSLayoutConstraint constraintWithItem:BarsSearchController.searchBar
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:searchBarView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[searchBarView addConstraint:[NSLayoutConstraint constraintWithItem:BarsSearchController.searchBar
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:searchBarView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
[searchBarView layoutIfNeeded];
self.definesPresentationContext = YES;
[BarsSearchController.searchBar sizeToFit];
What am I missing here?
BarsSearchController.hidesNavigationBarDuringPresentation = YES;
I have the same problem, to fix it, I hide back button and background in viewDidLoad :
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
[[[self navigationController] navigationBar] setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[[[self navigationController] navigationBar] setShadowImage:[UIImage new]];
[[[self navigationController] navigationBar] setTranslucent:YES];
}
and I hide navigationBar in viewDidAppear :
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[self navigationController] setNavigationBarHidden:YES];
}

UINavigationController subview's won't come back after hide/show of navigation bar

I'm struggling with a subview of my navigation controller whose position and dimensions are constricted to the navigation bar. When I hide the navigation bar, the subview disappears (expected behavior). But when I'm bringing the navigation bar back the subview does not follow.
This subview is just a progress view (a custom UIView that I fill with a color depending on the progress of some tasks) on top of my navigation bar.
The positionning of the progress view is set with constraints in the viewDidLoad of my UINavigationController subclass :
- (void) viewDidLoad{
// The dimensions here are totally arbitrary since they are going to be defined automatically by the constraints
_progressView = [[CRProgressView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[self.view addSubview:_progressView];
UINavigationBar *navBar = [self navigationBar];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:_progressView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:navBar
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:_progressView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:navBar
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:_progressView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:navBar
attribute:NSLayoutAttributeRight
multiplier:1
constant:0]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:_progressView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:navBar
attribute:NSLayoutAttributeHeight
multiplier:0
constant:2]];
[_progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
}
From here everything looks fine but at some point, in the collectionView displayed in the navigation controller I call [self.navigationController setNavigationBarHidden:YES animated:YES] to clear the screen and have extra space when the user scrolls down in the collection view.
If the user scrolls up I show the navigation bar back but here the navigation bar appears without the progress view.
Here is the code used for showing and hiding the navigation bar in the collectionView, but I don't think the issue comes from here. It correctly hides and shows the navigationBar :
- (void) scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y <= scrollView.contentSize.height - self.view.frame.size.height) {
if (self.lastContentOffsetY > scrollView.contentOffset.y) {
//Show navigation bar
if (self.navigationController.navigationBarHidden) {
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
}
else if (self.lastContentOffsetY < scrollView.contentOffset.y) {
if (!self.navigationController.navigationBarHidden) {
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
}
self.lastContentOffsetY = scrollView.contentOffset.y;
}
}
I don't understand why the progress view does not come back with the navigation bar when I'm calling [self.navigationController setNavigationBarHidden:NO animated:YES].
Any help will be greatly appreciated. Thanks in advance.
I'm not sure if it works 100% in your case, but you could try inserting this override method:
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated{
[super setNavigationBarHidden:hidden animated: animated];
[self layoutIfNeeded];
}

iOS - Pure AutoLayout and UIScrollView not scrolling

This is my first time using UIScrollViews with a pure Autolayout approach. This is what the view hierarchy looks like
view
-scrollview
--view1
--view2
--view3
scrollview should contain view1|view2|view3 in that order.
I set the scrollviews width, height, centerx and bottom space to superview. The view1, view2 and view3 that are created all have their width and height constraints setup in their updateConstraints method. Additionally, some constraints are provided in code. What is the reason this scrollview is not scrolling from left to right? I have read literally all of the guides I can find online about creating and adding subviews to a UIScrollView programmatically with auto layout. I found some mention about having to provide four different constraints, leading, trailing, top and bottom for each view added as a subview to the scrollview. Are these the only NSLayoutAttributes that one can specify? How do attributes such as NSLayoutAttribueLeft or NSLayoutAttribueRight relate? I have read documentation on Apples website as well, specifically https://developer.apple.com/library/ios/technotes/tn2154/_index.html. I am attaching the setup I currently have. Everything is done via code.
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataSource = #[ [[PCCGenericRating alloc] initWithTitle:#"Easiness"
andMessage:#"WHAT A JOKERRRR"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]],
[[PCCGenericRating alloc] initWithTitle:#"Joker"
andMessage:#"WHAT A JOKERRRR"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]],
[[PCCGenericRating alloc] initWithTitle:#"Difficulty"
andMessage:#"YOu are not difficult at all"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]]
];
[self initView];
}
- (void)initView {
CGFloat navigationBarHeight = self.navigationController.navigationBar.frame.size.height;
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
CGFloat heightDifference = navigationBarHeight + statusBarHeight;
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.delegate = self;
[self.scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
self.scrollView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.scrollView];
//setup constraints
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:-heightDifference]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.0]];
[self.dataSource enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
PCCGenericRating *rating = (PCCGenericRating *)obj;
PCCGenericRatingView *ratingView = [self createViewWithRating:rating];
[self.scrollView addSubview:ratingView];
int multiplier = (idx == 0) ? 1 : (int) (idx + 1) ;
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:ratingView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterX
multiplier:multiplier
constant:0.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:ratingView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]];
}];
}
- (PCCGenericRatingView *)createViewWithRating:(PCCGenericRating *)rating {
PCCGenericRatingView *view = [PCCGenericRatingView genericRatingViewWithTitle:rating.title andMessage:rating.message];
return view;
}
Upon printing out the scrollview constraints, they look okay to me:
po self.scrollView.constraints
<__NSArrayM 0x115b051f0>(
<NSLayoutConstraint:0x1145d9290 PCCGenericRatingView:0x114579880.centerX == UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145d9410 PCCGenericRatingView:0x114579880.centerY == UIScrollView:0x11458d4b0.centerY>,
<NSLayoutConstraint:0x1145d9dd0 PCCGenericRatingView:0x1145d9560.centerX == 2*UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145d9e40 PCCGenericRatingView:0x1145d9560.centerY == UIScrollView:0x11458d4b0.centerY>,
<NSLayoutConstraint:0x1145da6b0 PCCGenericRatingView:0x1145d9e90.centerX == 3*UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145da730 PCCGenericRatingView:0x1145d9e90.centerY == UIScrollView:0x11458d4b0.centerY>
)
Here is a screenshot of what it looks like:
I find it odd that the last element in the datasource is the first view controller showing up in the scrollview, when it should be the last view. It also doesn't scroll left to right as it should.
Make sure your top_constraint for the view1 and bottom_constraint for view3 will be as per your scrollView's constraints. Otherwise scrollview's contentSize: {0, 0}.
Wherever you are printing your constraints, try printing scrollview.contentSize, it will likely be 0,0 and that is where your problem is. As far as I know, and as you mentioned in your post, you have to explicitly set the subviews of a scrollview to the scrollviews top bottom left and right constraints. Setting these automatically sets the contentSize of the scrollview which will enable it to scroll. It looks like you are only setting centerX and centerY constraints which will not set the scrollviews contentSize to what you need.
Try setting these programatically (this is pseudocode but you get the idea):
view1.topConstraint = scrollView.topConstraint
view1.leftConstraint = scrollView.leftConstraint
view3.bottomConstraint = scrollView.bottomConstraint
view3.rightConstraint = scrollView.rightConstraint
If you set all of those correctly, your scrollview will scroll properly. Just remember to check the contentsize, and if the contentsize is 0,0 then your constraints aren't properly set up.

Subviews Not Positioned Correctly During Animation

I have a simple animation where I'm expanding a new view from the center of the old one, while that one fades out. However, the subviews of this new view (a button and a label) "fly" in from off the lower right hand side of the screen as that new view expands to take up the whole screen. I've tried with and without autolayout turned on, and while the two scenarios give different results, they're both wrong.
The set up is simple, I have two unconnected view controllers in a storyboard, and use the following code to animate the view change:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
CGRect frame = self.view.frame;
[UIView animateWithDuration:5 animations:^{
yellow.view.frame = frame;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
The question is, why don't the subviews stay where they're supposed to inside their superview as it animates.
To grow a subview into place, particularly when using Autolayout, it is far simpler to animate the transform property instead of the frame. This way, the subview's bounds property does not change, so it does not feel the need to relay out its subviews all the time during the animation.
Add your subview with its final frame, set its transform to be a scaling affine transform of, say, 0.1, then animate it to the identity transform. It will grow out from the center point, with all subviews in the correct position.
The problem was with the layout constraints. If instead of setting the view frame in the animation block, I add constraints between the window and the new view, then call layoutIfNeeded in the animation block it works correctly:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
[yellow.view setTranslatesAutoresizingMaskIntoConstraints:NO];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeLeading relatedBy:0 toItem:win attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:win attribute:NSLayoutAttributeTop multiplier:1 constant:20];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTrailing relatedBy:0 toItem:win attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeBottom relatedBy:0 toItem:win attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[win addConstraints:#[con1,con2,con3,con4]];
[UIView animateWithDuration:1 animations:^{
[win layoutIfNeeded];
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}

Resources