I have a UITableView which I am able to add a header view to fairly easily. Many apps (like Facebook, for viewing events) have a headerView that when you pull down, the header view stays put but the rest of the table (the UITableViewCell's) are bouncing. When scrolling up the header disappears. How can I achieve this functionality?
Right now when I pull down the UITableView, even the headerView bounces as well
You can achieve this effect quite easily by adding a subview to the header view and adjusting its frame or transform when the table view is scrolled beyond the top, i.e. the y component of its contentOffset becomes negative.
Example (in a UITableViewController subclass):
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat headerHeight = 64.0f;
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, headerHeight)];
UIView *headerContentView = [[UIView alloc] initWithFrame:headerView.bounds];
headerContentView.backgroundColor = [UIColor greenColor];
headerContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[headerView addSubview:headerContentView];
self.tableView.tableHeaderView = headerView;
}
//Note: UITableView is a subclass of UIScrollView, so we
// can use UIScrollViewDelegate methods.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offsetY = scrollView.contentOffset.y;
UIView *headerContentView = self.tableView.tableHeaderView.subviews[0];
headerContentView.transform = CGAffineTransformMakeTranslation(0, MIN(offsetY, 0));
}
(to keep it simple, I've just used the first subview of the actual header view in scrollViewDidScroll:, you may want to use a property for that instead.)
Your UITableView is most likely working properly. Section headers are sticky by default in Plain style tables. Meaning as you scroll down the header stays at the top of the UITableView's frame until the next section header pushes it out of the way. The opposite occurs when you scroll up. Conversely you get the sticky behavior on section footers at the bottom of the UITableView's frame.
EDIT Misunderstood the original question:
I would suggest using a section header rather than the table view header to get the sticky behavior you're looking for.
Include a section in your data with no rows and put your table header's view in that section header view.
you can use this line in view did load: (swift 5.6)
tableView.bounces = false
There is 2 ways you can set the table header:
Using the .tableHeaderView property directly (this header scrolls with the table)
Overriding the - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section function (this header stays static with the section)
By the sounds of it you should use the 2nd method instead of using the .tableHeaderView property
Related
I am writing because I have a problem with the Auto Layout.
I'm trying to create a simple view in InterfaceBuilder with Auto Layout I want to load code and enter as a header of a table (not as header section). I explain briefly what are the characteristics.
The imageView must be square and must be as wide as the screen.
The space under the picture to the bottom of view that contains the button and label must be high 50 points.
Between image and button has to be a fixed distance of 12 points.
Between image and label must be a fixed distance of 13 points.
All these features are able to get them with Auto Layout. I added a constraint to the aspect ratio of the image (1: 1) and the various constraints for distances. all right.
The real problem is that by launching the app on iphone 6+ simulator (414 points of width), the image (with the label and button) goes above the cells.
Enabling various transparencies I noticed that the superView of Image View, only increase the width. It does not increase its height! How do I fix?
This is the code:
- (void)viewDidLoad{
//...
PhotoDetailsHeaderView *hView = (PhotoDetailsHeaderView *)[[[NSBundle mainBundle] loadNibNamed:#"PhotoDetailsHeaderView" owner:self options:nil] objectAtIndex:0];
hView.delegate = self;
self.tableView.tableHeaderView = hView;
//...
}
This is how I create the xib:
and this is how it is on the simulator, the green box is Uiimageview and the yellow box (under green box) is the mainview (or superview):
How can fix it?
Many thanks to all!
You'll need to add a property to store your PhotoDetailsHeaderView:
#property (nonatomic, strong) PhotoDetailsHeaderView *headerView;
Then calculate its expected frame in viewDidLayoutSubviews. If it needs updating, update its frame and re-set the tableHeaderView property. This last step will force the tableView to adapt to the header's updated frame.
- (void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
CGRect expectedFrame = CGRectMake(0.0,0.0,self.tableview.size.width,self.tableView.size.width + 50.0);
if (!CGRectEqualToRect(self.headerView.frame, expectedFrame)) {
self.headerView.frame = expectedFrame;
self.tableView.tableHeaderView = self.headerView;
}
}
The problem is probably that in iOS you have to reset the header of the table view manually (if it has changed its size). Try something along these lines:
CGRect newFrame = imageView.frame;
imageView.size.height = imageView.size.width;
imageView.frame = newFrame;
[self.tableView setTableHeaderView:imageView];
This code should be in -(void)viewDidLayoutSubviews method of your view controller.
My UICollectionView cells contain UILabels with multiline text. I don't know the height of the cells until the text has been set on the label.
-(CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
This was the initial method I looked at to size the cells. However, this is called BEFORE the cells are created out of the storyboard.
Is there a way to layout the collection and size the cells AFTER they have been rendered, and I know the actual size of the cell?
I think your are looking for the invalidateLayout method you can call on the .collectionViewLayout property of your UICollectionView. This method regenerates your layout, which in your case means also calling -collectionView: layout: sizeForItemAtIndexPath:, which is the right place to reflect your desired item size. Jirune points the right direction on how to calculate them.
An example for the usage of invalidateLayout can be found here. Also consult the UICollectionViewLayout documentation on that method:
Invalidates the current layout and triggers a layout update.
Discussion:
You can call this method at any time to update the layout information. This method invalidates the layout of the collection view itself and returns right away. Thus, you can call this method multiple times from the same block of code without triggering multiple layout updates. The actual layout update occurs during the next view layout update cycle.
Edit:
For storyboard collection view which contains auto layout constraints, you need to override viewDidLayoutSubviews method of UIViewController and call invalidateLayout collection view layout in this method.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[yourCollectionView.collectionViewLayout invalidateLayout];
}
subclass UICollectionViewCell and override layoutSubviews like this
hereby you will anchor cell leading and trailing edge to collectionView
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectMake(0, self.frame.origin.y, self.superview!.frame.size.width, self.frame.size.height)
}
Hey in the above delegate method itself, you can calculate the UILabel size using the below tricky way and return the UICollectionViewCell size based on that calculation.
// Calculate the expected size based on the font and
// linebreak mode of your label
CGSize maximumLabelSize = CGSizeMake(9999,9999);
CGSize expectedLabelSize =
[[self.dataSource objectAtIndex:indexPath.item]
sizeWithFont:[UIFont fontWithName:#"Arial" size:18.0f]
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
- (void)viewDidLoad {
self.collectionView.prefetchingEnabled = NO;
}
In iOS 10, prefetchingEnabled is YES by default. When YES, the collection view requests cells in advance of when they will be displayed. It leads to crash in iOS 10
I have 2 tables in one view. Table A lists a bunch of users. Table B lists a users objects. When a row is selected in Table A, Table B is reloaded with the objects that belong to that user.
So when a user selects a row in Table A, the image in the background of the cell changes to the highlighted version of the image.
Here is the normal version of the background image:
Here is the highlighted version of the background image:
As you can see, the highlighted version has a small arrow on the right of it. This arrow is beyond the width of the table cell the table itself. When the row is selected, the image changes as it should, but the image is sized down to fit the whole image into the cell.
What I would like to happen is the image goes outside of the table, or on top of the table for that selected row.
One possible solution I thought was to center the table on the selected row and then overlay that image, but if the user was to try to scroll through the table, the image would need to move and that would be a big pain.
So what I would like to know is it is possible to extend the cell's size beyond the table one it is selected?
Thanks in advance!
EDIT:
The following does not work, just in case anyone was going to try:
[cell setFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y, cell.frame.size.width+20, cell.frame.size.height)];
Setting a views clipsToBounds property to NO will allow the view to draw outside of its own frame.
In your UITableViewController subclass:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do what you normally do here, if anything then add...
self.view.clipsToBounds = NO;
}
Doing this has a side effect where you will see full cells be created at the bottom or top of the tableview instead of them scrolling partially into view. Either put the table view into another view that has clipsToBounds set to YES, align the edges of the table view with the edges of the screen, or have views covering over the bottom and top (like a UIToolbar and UINavigationBar normally would).
To get the UITableViewCell's selectedBackgroundView to extend past the table view's frame create a subclass of UITableViewCell and override the layoutSubviews method.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YQGyZ"]];
self.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"CQXYh"]];
self.textLabel.backgroundColor = [UIColor clearColor];
self.detailTextLabel.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frame = self.selectedBackgroundView.frame;
frame.size.width += 13; // where 13 is the size of the arrow overhang from the same images
self.selectedBackgroundView.frame = frame;
// You can also change the location of the default labels (set their background colors to clear to see the background under them)
self.textLabel.frame = CGRectMake(70, 0, 148, 30);
self.detailTextLabel.frame = CGRectMake(70, 30, 148, 30);
}
Good luck.
I would recommend modifying the image resource of the unselected cell background such that it is the same width as the selected background, but just has a transparent rectangle on the side.
I have a UITableView with 3 sections. Each of them have a footer that I've added using viewForFooterInSection. The problem I'm having is that when I scroll the tableview down, the footer sticks to the bottom of the screen, and doesn't scroll like the rest of the cells. Does anyone know how to make it so the footer almost acts like a cell, and scrolls along with the rest of the table? Thanks!
I actually figured it out. Probably isn't the smartest way to do it, but I changed my UITableView style to grouped, and that fixed it. I had to tweak the TableView a bit so the cells would look the same as they did non-grouped (clear background, no separators), but it worked fine. The footer no longer sticks to the bottom of the TableView.
Footers are supposed to stick. The trick is to add an extra cell to each section and render the footer there. If you need more help, add a comment, but it should be pretty straightforward.
EDITS:
Q: Alright. I'm using Core Data and NSFetchedResultsController, to populate the TableView. Would that make it more tricky to accomplish this?
A: Not at all. Override
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
To add an extra cell in each section, and in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
test if the indexPath.row > than your fetchedResultsController's rows for that section. If true, add in your cell that shows the footer information.
One way around this is if you set the footer as one of the cells as the last cell in the scroll view (could be done but setting it as the last item in the array that you set the uitable from)
Adding an extra cell, making it invisible, and rendering your view there is not an advisable way of adding a footer. Doing it properly is pretty straight-forward:
- (UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section {
NSString* sectionFooter = [self tableView:tableView titleForFooterInSection:section];
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, yourWidth, yourHeight)]; //create a view- the width should usually be the width of the screen
UILabel* label = [[UILabel alloc] initWithFrame:someFrame];
label.backgroundColor = [UIColor blueColor];
label.textColor = [UIColor whiteColor];
label.text = sectionFooter;
[view addSubview:label];
return view;
}
You will also have to implement tableView: titleForFooterInSection: if you want to add text like I have here.
I did had similar problem when I found out that my code did not work with Landscape mode, and only worked in Portrait mode. In my old code, when in Landscape the tableview can not scroll lower than visible view and it bounced back to top row when scroll (not letting me see the lower rows). All I have to change is to make sure that the height set to 44 as default cell height. So my footer is basically another cell with clearColor. Note my app uses 'AutoLayout'
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
CGRect screenBounds = [UIScreen mainScreen].bounds;
CGFloat width = screenBounds.size.width;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
view.backgroundColor = [UIColor clearColor];
UILabel *label = [[UILabel alloc] initWithFrame:view.frame];
label.text = #"Your Text";
[view addSubview:label];
return view;
}
I'm trying to add one subview (view from an NSViewController) for every element in a dictionary to a NSScrollView to get kind of a tableview, but with much more flexibility over the cells.
Is it possible to place (programmatically) e.g. 100 subviews underneath each other so that you have to scroll down the NSScrollView to get to the last element?
The short answer is yes. I have done this before, and I assure you that it will work. Let me also assure you that it is not quite as simple as it looks ;)
The way to do this is to maintain one content view, in which you add all of your custom rows as subviews programmatically. Note that you will either have to resize the contentView before adding all of the rows, or you will have to turn off autoresizing for the row views. Set the documentView of your scrollView to this custom contentView, and you should be good to go.
Yes, simply initialize the views programmatically using (i.e.)
NSView *subView = [[NSView alloc] initWithFrame:CGRectMake(10,10,100,100)];
then add to the main using addSubview: method of the main view.
Remember to manually release the subview when you've done with it (that means, when you have added it to the main view).
As example you can do something like
int height x = 10, y = 10, width = 100, height = 100;
for(int i = 0;i<100;i++) {
NSView *subView = [[NSView alloc] initWithFrame:CGRectMake(x,y + height*i,width,height)];
[scrollView addSubview:subView];
[subView release];
}