Locking UISearchBar on top of TableView - iphone

I wanted to lock search bar on top of table view when y boundary reaches 0 ( or beyond - value)
I tried to in scroll view delegate method, but nothing really changed. In fact, the search bar's frame changed, but it still behaved as default.
Any ideas?
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = self.searchDisplayController.searchBar;
CGRect rect = searchBar.frame;
rect.origin.y = MAX(0, scrollView.contentOffset.y);
self.searchDisplayController.searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
}

In your viewdidload method you need to offset the table view:
self.tableView.contentOffset = CGPointMake(0.0, 44.0);
Then In IB drag a searchbar above your UITableview in your UINavigation controller
Or prgrammically add it.

You need to call setNeedsLayout if you're running on iOS 6.0+.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = self.searchDisplayController.searchBar;
CGRect rect = searchBar.frame;
rect.origin.y = MAX(0, scrollView.contentOffset.y);
[scrollView setNeedsLayout]; // <-- Call setNeedsLayout here.
self.searchDisplayController.searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
}

Related

UITableViewCell custom subclass view not updated

In my custom table view cell subclass, the location of one of the textlabel depends on the content of an ivar (NSString). (i.e: if the NSString is the empty string, the location of the textlabel's frame is different).
The position if updated as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
customOverlayCell *myCell = [self.tableView dequeueReusableCellWithIdentifier:#"CustomOverlayCell"];
if ([buildingFName isEqual:#""])
{
CGRect titleLabelFrame = myCell.titleLabel.frame;
titleLabelFrame.origin.y = 45;
[myCell.titleLabel setFrame:titleLabelFrame];
}
return myCell;
}
I have removed parts of code that weren't relevant.
The result is that the layout of the first cells that appear on the screen are properly updated, but the layout of the views that appear after scrolling down aren't updated.
Am I not using dequeueReusableCellWithIdentifier properly? Or is anything else wrong?
Edit:
Solution from EJV:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
titleLabelFrame.origin.y = 37;
}
[myCell.titleLabel setFrame:titleLabelFrame];
If the frame of the title label is dynamic, then when you dequeue a cell from the table view, the frame could be in either of the two states (when buildingFName is empty and when it has characters). You need to make sure that you set the frame for when buildingFName is not empty. That way, the title label's frame will always be set correctly. So, you need code like this:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
// Change titleLabelFrame
}
[myCell.titleLabel setFrame:titleLabelFrame];
I'm afraid, it would require subclassing the cell and implementing [UITableViewCell layoutSubviews] to properly lay out your subviews of the cell. This is how I did something similar for a switch table view cell:
- (void)layoutSubviews
{
CGFloat const ESCFieldPadding = 10.0f;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
// call super layout
[super layoutSubviews];
// obtain widths of elements
CGFloat contentWidth = self.contentView.frame.size.width;
CGFloat contentHeight = self.contentView.frame.size.height;
CGFloat switchWidth = self.switchView.frame.size.width;
CGFloat switchHeight = self.switchView.frame.size.height;
CGFloat labelWidth = contentWidth - (4 * ESCFieldPadding) - switchWidth;
// correctly position both views
self.textLabel.frame = CGRectMake(ESCFieldPadding, 0.0f,
labelWidth, contentHeight);
// it is needed to explicitly resize font as for some strange reason,
// uikit will upsize the font after relayout
self.textLabel.font = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]];
CGRect switchFrame = self.switchView.frame;
switchFrame.origin = CGPointMake(contentWidth - ESCFieldPadding - switchWidth,
(contentHeight / 2) - (switchHeight / 2));
self.switchView.frame = CGRectIntegral(switchFrame);
[UIView commitAnimations];
}
try to disable autolayout from your cell

Resize a UITableView on scroll

I've got a VC with a table view. When an event occurs, I want to drop in a UIView from the top of the main view. When the user scrolls the table view, I want to re-layout the view so that the dropped in view "scrolls away". I figured I'd do this by moving the upperView's frame and resizing the table view (both in relation to the scroll offset). I've got it almost working as follows:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
if (contentOffsetY > 0) {
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
}
NSLog(#"%f", self.upperView.frame.origin.y);
}
The upper view origin starts at 0,0.
The problem is, after a little dragging back and forth, I lose the top few pixels of that upper view. It can't seem to get it's origin y back to zero. The logging reads negative values, and only gets to -1, with the most careful dragging. Has anybody done something like this? Much obliged if you can help.
It sounds like you always scroll the table view to the top when you show the drop-in view. Assuming that's the case, there is a better way to do this.
UITableView inherits the contentInset property from UIScrollView. The contentInset property defines a border on each edge of the scroll view. Each border has its own thickness, which is zero by default. These borders just affect how far the scroll view is willing to let the user scroll the content - they don't hide the content! If you set the top inset larger than zero, and give the scroll view a subview with a negative Y origin, that subview can be visible in the border, and will scroll with the rest of the scroll view's content.
So we'll set the table view's top inset to the height of the drop-in view, and add the drop-in view as a subview of the table view with its origin set to the negative of its height. This will make it fit perfectly on the screen above the first row of the table view, and it will scroll with the table view. When we detect that the drop-in view has been scrolled fully off-screen, we can just remove it from the table view and set the table view's top inset back to zero.
We'll need an instance variable that tracks the current state of the drop-in view:
typedef enum {
DropInViewStateHidden,
DropInViewStateAppearing,
DropInViewStateVisible
} DropInViewState;
#implementation ViewController {
DropInViewState _dropInViewState;
}
In my test project, I just used a button to trigger the drop-in view. Here's the action:
- (IBAction)dropIn {
if (_dropInViewState != DropInViewStateHidden)
return;
CGRect frame = self.dropInView.frame;
frame.origin.y = -frame.size.height;
self.dropInView.frame = frame;
[self.tableView addSubview:self.dropInView];
self.tableView.contentInset = UIEdgeInsetsMake(frame.size.height, 0, 0, 0);
[self.tableView setContentOffset:frame.origin animated:YES];
_dropInViewState = DropInViewStateAppearing;
}
When the table view scrolls, we check the state of the drop-in view. If it is in the “visible” state and has been scrolled off-screen, we hide it. There's a tricky bit because when we make the drop-in view visible, and scroll it onto the screen, we can receive scrollViewDidScroll: messages that would make us think the drop-in view has been hidden. That's why we start out in the DropInViewStateAppearing state, and transition to the DropInViewVisible state when we know the view has appeared.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
switch (_dropInViewState) {
case DropInViewStateHidden:
break;
case DropInViewStateVisible:
if (scrollView.contentOffset.y >= 0) {
// dropInView has been scrolled off-screen
self.tableView.contentInset = UIEdgeInsetsZero;
[self.dropInView removeFromSuperview];
_dropInViewState = DropInViewStateHidden;
break;
}
case DropInViewStateAppearing:
// When I first add dropInView to tableView and tell tableView
// to scroll to reveal dropInView, I may get a bunch of
// scrollViewDidScroll: messages with contentOffset.y >= 0.
// I don't want those messages to hide dropInView, so I sit in
// DropInViewStateAppearing until contentOffset.y goes negative,
// which means at least part of dropInView is actually on-screen.
if (scrollView.contentOffset.y < 0)
_dropInViewState = DropInViewStateVisible;
break;
}
}
Figured this out: The UITableView doesn't thoroughly message didScroll during the bounce. This is why I was missing a few pixels. Resizing during the bounce makes the bounce get mixed up and stop. This fix on my code above allows the bounce to work (by moving, not resizing the table) and makes sure the upper view is correctly placed during the bounce (when contentOffset.y <= 0).
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
if (contentOffsetY > 0) {
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
} else {
self.upperView.frame = CGRectMake(0, 0, 320, upperHeight);
scrollView.frame = CGRectMake(0.0, upperHeight, 320, scrollView.frame.size.height);
}
[super scrollViewDidScroll:scrollView];
}

Hide UITableView search bar by default when in a navigation controller

I've read multiple posts on this but it's not working properly for me. I'm using the latest 4.2 SDK.
The code I have is
self.tableView.contentOffset = CGPointMake(0.0, 44.0);
This partially works, it moves the search bar up a little bit, but it does not get hidden completely. I've tried increasing the value 44 to something greater and this had no affect what so ever! I'm calling this code in the viewDidLoad method of the table's view controller. Does anyone have any ideas?
Another approach should be...in viewDidLoad call:
self.tableView.contentInset = UIEdgeInsetsMake(-self.searchDisplayController.searchBar.frame.size.height, 0, 0, 0);
and implementing endDragging delegate method:
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
CGPoint offset = self.tableView.contentOffset;
CGFloat barHeight = self.searchDisplayController.searchBar.frame.size.height;
if (offset.y <= barHeight/2.0f) {
self.tableView.contentInset = UIEdgeInsetsZero;
} else {
self.tableView.contentInset = UIEdgeInsetsMake(-barHeight, 0, 0, 0);
}
self.tableView.contentOffset = offset;
}
setting content is to remove some "flickering"
also if You want searchbar to stick at the top, implement didScroll this way:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGRect sbFrame = self.searchDisplayController.searchBar.frame;
sbFrame.origin.y = self.tableView.contentOffset.y;
if (sbFrame.origin.y > 0) {
sbFrame.origin.y = 0;
}
self.searchDisplayController.searchBar.frame = sbFrame;
}
I hope this will help (took me few days to figure out:) )
Cheers!
UPDATE:
As #carbonr noted You should add this line in viewDidLoad since iOS7+
self.edgesForExtendedLayout = UIRectEdgeNone;
self.tableView.contentOffset = CGPointMake(0.0, 44.0);
The above code does in fact work but it needs to run after the UITableView has finished creating all of its cells. I guess thats another question though.
You can set the initial bounds of the table view inside viewDidLoad, so the search bar appears hidden at the beginning.
You have to create the searchBar property and then use following code:
- (void)viewDidLoad
{
//...
CGRect bounds = self.tableView.bounds;
bounds.origin.y = self.tableView.bounds.origin.y + searchBar.bounds.size.height;
self.tableView.bounds = bounds;
//...
}
For others still looking for an updated solution, you can check out my answer over here.
Basically you need to update the contentOffset the first time viewDidLayoutSubviews is called.
I also have the same problem like yours. The following code solved my problem.Please add the code in you viewDidLoad() :
self.edgesForExtendedLayout = UIRectEdgeNone;
N:B: I used autoLayout in my project.

UIScrollView- How far is it scrolled?

I'm using an UIScrollView and I have an image that indicates to the user that there is more content that they can scroll through vertically. I would like this image to be hidden when the scrollview is all the way at the bottom. Is there a way to do this? Would I have to subclass UIScrollView and make my own?
your scroll view's delegate should repsond to scrollViewDidEndScrollingAnimation: and use that to check where you are
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
// Get some details about the view
CGSize size = [scrollView frame].size;
CGPoint offset = [scrollView contentOffset];
CGSize contentSize = [scrollView contentSize];
// Are we at the bottom?
if (-offset.y + size.height <= contentSize.height)
NSLog(#"bottom");
else
NSLog(#"not bottom");
}
NB The if statement was done in my head so it might be the wrong way round ;)

Dynamically changing position of a UIView

Here's my setup. I have a viewcontroller that I'm creating and adding as a subview. The viewcontroller presents some options that a user can chose from. The viewcontroller is being pushed in response to a "long press" gesture. Within the viewcontroller, I added a child UIView to group some other controls together so I can move them around the screen as a unit and, when they are displayed, center them on the location of the long press. Here is the code that instantiates the view controller, changes its location, and adds it as a subview:
UserOptions *opts = [[UserOptions alloc] initWithNibName:#"UserOptions" bundle:nil];
[opts recenterOptions:location];
[self.view addSubview:opts.view];
That bit of code does create and push the viewcontroller, but the call to recenterOptions doesn't do anything. Here is that method:
- (void) recenterOptions:(CGPoint)location {
CGRect oldFrame = self.optionsView.frame;
CGFloat newX = location.x; // + oldFrame.size.width / 2.0;
CGFloat newY = location.y; // + oldFrame.size.height / 2.0;
CGRect newFrame = CGRectMake(newX, newY, oldFrame.size.width, oldFrame.size.height);
self.optionsView.frame = newFrame;
}
Note that self.optionsView is the child UIView that I added to the viewcontroller's nib.
Does anyone know why I'm unable to change the location of the UIView?
Regards,
Eric
A couple things. First, try adding the view to the view hierarchy before calling -recenterOptions:
UserOptions *opts = [[UserOptions alloc] initWithNibName:#"UserOptions"
bundle:nil];
[self.view addSubview:opts.view];
[opts recenterOptions:location];
Next, just set the center of the view instead of trying to change its frame:
- (void) recenterOptions:(CGPoint)location {
[[self optionsView] setCenter:location];
}
Are you verifying that your optionsView is valid (non-nil)?
I agree with Rob, btw. You need to change your title to match your question.
Views are loaded lazily. Until you call self.view, the view is not loaded and optionsView is nil, hence self.optionsView.frame = newFrame does nothing.