I am creating an app and in one of the navigation views, I have a very similar design as that of the App Store app — see Details | Reviews | Related section. Following on similar lines, I wish to implement the segmented control in the 'same' way Apple has done in their app. (This is also similar to what Apple does in the Artist -> Albums navigation view in the default iOS 7 music app, albeit for a table header (maybe).)
If you scroll up, when the segmented control container touches the navigation bar, it sticks there.
It also allows the user to notice that this is some kind of overlay due to the alpha associated with it.
When you scroll down, it moves into position when required.
What I have done —
I have created a container view with the segmented control. When the scrollView scrolls, I reposition my container view to accomplish the sticky effect. This is just pseudo-code but my code actually works.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.labTestScrollView)
{
/*
As soon as the user scrolls up, y changes, therefore, we need to stick the options container view
such that it doesn't go past the UINavigationBar
Need to check if the origin of the options container view in the coordinate system of the main
superview is just gone past the UINavigationBar in this controller's view.
- get the bounds rect for the options container view
- convert these bounds and position them in the coordinates of this controller's view
- check if origin.x for container view is less than that of view.origin.y
- if less than stick it by converting the headerFrame to the coordinate system of the options
container view; and raise the stuck flag
- if greater, check if the stuck flag is raised; check for another object in space before the container view and adjust accordingly.
*/
}
}
There are two issues:
No overlay effect. I can configure the alpha such that the effect is a bit more visible but that doesn't seem natural.
The second concern stems from the first. This seems like a very specific solution. I am looking forward to something that's more natural; and something that could work by default using table views or something.
Why not simply use a UITableView?
Put your 'top content' in section 0 and have no header for that section. Put all the other stuff in section 1 and give that section a header with your UISegmentedControl.
Following code works pretty well. You might want to find a way to give the background of the header a 'blur' effect to mimic Apple's behavior some more; maybe GPUimage could help you there?
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return section == 0 ? 1 : 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = indexPath.section == 0 ? #"header" : #"content";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
// customize cell
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if(section == 1) {
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:#[#"Foo", #"Bar"]];
segmentedControl.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.95];
return segmentedControl;
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return section == 0 ? 0 : 44;
}
I'll leave the tweaking up to you ;)
This is actually 2 views.
The first is the header of a UITableView.
This includes the segmented control and the app icon, the install button, etc...
The second view has a segmented control that sits at the top of the screen and is hidden.
When the tableview scrolls you can intercept the scroll view delegate method scrollView:didScroll...
In here, if the scrollView offset is larger than the height of the header then make the second view visible. Else make it hidden.
That's it. Now you just need to remember to update both segmented controls whenever one is hanged.
Related
I've got a project (that has been written by other people) where there's a feed with content and text displayed inside a table view. Each post corresponds to one section in table view, and each section has its own rows corresponding to elements like content, text, like button etc.
I need to display a short label for post captions with a "more" button inside table view cell, and when more button is tapped, the label will expand to whatever size the caption fits, all happening inside a table view cell. When the more button is tapped I change the label's numberOfLines property to zero, and as the cells have automatic height, all I need is to reload that particular caption cell. (the cell displays correctly with the expanded size if I set numberOfLines to 0 at the first place before displaying the cell.)
I've tried:
[tableView beginUpdates];
tableView endUpdates];
I've tried various animation options with:
[tableView reloadRowsAtIndexPaths:#[myPath] withRowAnimation:UITableViewRowAnimation(Bottom,Top,None etc)];
I've tried:
[tableView reloadSections:[NSIndexSet indexSetWithIndex:myPath.section] withRowAnimation:UITableViewRowAnimation(Top,Bottom,None etc)];
But they all yield the same result: the whole table view layout gets messed up: it jumps to another cell, some views go blank, cells overlap each other, video inside cells stop playing, and the label doesn't expand (but refreshes inside itself, e.g. that short preview txt with one line animates from top/bottom etc but doesn't expand).
What might be causing the mess up of the whole table view and how I can reload just one cell correctly with and expansion animation, without messing up the whole layout? I've seen many questions and answers regarding this, but they all recommend the options that I've already tried and explained above.
My app targets iOS 8.0+
UPDATE: Here is the relevant code (with some parts regarding inner workings that aren't related to layout, removed):
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier: MyCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.indexPathToReloadOnAnimation = indexPath;
cell.shouldShortenCaption = YES;
id post = self.posts[indexPath.section] ;
[cell setPost:post];
return cell;
And inside setPost::
if(self.shouldShortenCaption){
captionLabel.numberOfLines = 2;
}else{
captionLabel.numberOfLines = 0;
}
NSString *text = [some calculated (deterministic) text from post object];
Button action is simple:
self.shouldShortenCaption = NO;
[one of the reload codes that I've written above in the question]
UPDATE 2: Here are some more methods regarding the issue:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section < self.posts.count) {
return 59;
}else
return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section < self.posts.count) {
MyFeedHeader *header = [tableView dequeueReusableCellWithIdentifier:MyFeedHeaderIdentifier];
header.delegate = self;
[header setPostIndex:section];
[header setPost:self.posts[section]] ;
return header;
}
else
return nil;
}
Header's setPost: method basically sets the relevant texts to labels (which have nothing to do with the caption, they are completely different cells. the problematic cell is not the header cell). The table doesn't have any footer methods. The only method regarding height is the one above.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section >= self.posts.count) {
return 1;
}
id post = self.posts[section];
[calculate number of rows, which is deterministic]
return [number of rows];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
BOOL hasCursor = self.cursor && self.hasMore ? 1 : 0;
return self.posts.count + hasCursor;
}
(Post count and cursor and hasMore are also deterministic).
UPDATE 3: I've asked a question (which wasn't a duplicate, even though there are similar questions) and got a useful answer that solved my problem. Can the downvoters please elaborate the reason that they've downvoted?
Here is an example: https://github.com/DonMag/DynamicCellHeight
Table B is one way of accomplishing "More/Less" (Table A was for another layout I played around with). It uses the [tableView beginUpdates]; tableView endUpdates]; method of triggering the table re-layout.
The key is getting all your constraints set up correctly, so the Auto-Layout engine does what you expect.
The example is in Swift, but should be really easily translated back to Obj-C (I think I did it in Obj-C first).
Edit: some additional notes...
This is using a pretty standard method of dynamic-height table view cells. The vertical spacing constraints between elements effectively "pushes out" the bounds of the cell. The tap here toggles the numberOfLines property of the label between 2 and 0 (zero meaning as many lines as necessary). Sequential calls to beginUpdates / endUpdates on the table view tells the auto-layout engine to recalculate the row heights without needing to reload the data.
For this example, I did use a little "trickery" to get the smooth expand/collapse effect... The multiline label you see here is contained in a UIView (with clipsToBounds true). There is a second, duplicate multiline label (alpha 0 so it's not visible) that is controlling the height. I found that changing the numberOfLines on the visible label sort of "snapped" to 2 lines, and then the size change animation took place... resulting in the text "jumping around."
Just for the heck of it, I added the "not so good" version to my GitHub repo for comparison's sake.
I am learning about UITableview on iOS and following a course online. I get the table showing fine, but the images on my cells are not all the way to the left (whereas the instructor's ones are). Here is a screenshot of the cells in question:
I don't want that gap, I want the images to be positioned right at the beggining of the cell, all the way to the left. I have done some research and it seems Apple has changed the default look of the cells between ios6 and ios7 so that now the images in cells show a little gap at the left. To get rid of it, I have tried UIEdgeInsets:
[tableView setSeparatorInset:UIEdgeInsetsZero];
and that's not working. I also have tried this approach:
cell.imageView.frame = CGRectMake( 0, 0, 50, 55 );
Nothing happens. So how would I go about it? Thanks
edit-------------------------------------------------------------------------------------------------------------------------------
Still not have found the answer to this. The solutions posted here don't work. I found this piece of code:
self.tableView.contentInset = UIEdgeInsetsMake(0, -50, 0, 0);
Which besides completely puzzling me (as the parameter affected should be the y?) I thought solved the issue by making the image on the cell appear all the way to the left, until I realised it only moved the whole view to the left (as I should have expected I guess) leaving an equal gap on the other side of the screen. All I want is for my images in the cells to appear all the way to the left of the cell as it used to be the case on previous ios. Thanks
It happens because default table content offset from left is 15, you should change it with 0.
See this once, you get idea Remove empty space before cells in UITableView
If you create custom cells. UITableViewCell have owner imageView. Change title of image in your cell.
If you use default cell, use custom cell with constraint Leading space = 0.
It is better not use default imageView of the cell. Drag and drop UIImageView from objective library, create a custom table view cell (Child class of UITableViewCell) then create and outlet of the image view just dragged.
The spacing in the UITableViewCell is because of the default TRUE returned by shouldIndentWhileEditingRowAtIndexPath method of UITableViewDelegate.
I was able to reproduce your problem by the below scenario:
UITableView is in editable mode:
self.tableView.editing = true
And you have implemented:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCellEditingStyleNone;
}
To correct your code:
If you do not want to set Editing Style then you can turn off the editing mode by
self.tableView.editing = false
and remove editingStyleForRowAtIndexPath.
Else if you need editing mode then set the appropiate Editing style(UITableViewCellEditingStyleDeleteor UITableViewCellEditingStyleInsert) or simply turn the indentation off.
- (BOOL)tableView:(UITableView *)tableView
shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
return FALSE;
}
You must create a custom cell, by adding a new class as a subclass of UITableViewCell. then you can design cell with autolayout and constraints which will resolve the issue.
there is a another concrete way to achieve this by creating subclass uitableviewcell (custom class).
steps to follow
create a class subclass of UITableViewCell.
in .h file create properties and outlets of UI components.
go to storyboard and add table view cell inside the tableview.
now add UI components like: imageview or button etc and set the x, y values according to.
make class of custom cell your className using identity inspector see image.
connect all outlets of UI components.
use below code uitableview
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = #"uniqueIdentifire";
yourCustomClassForCell *cell = (yourCustomClassForCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil){
cell = [[yourCustomClassForCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier];
}
cell.imageView.image = [imageAry objectAtIndex:indexPath.row];
}
Dont forget to give identifire by selecting your cell using storyboard Attribute inspector uniqueIdentifire to identifire property see image.
Also you can give some vertical space between cells by just to add this below code (Method only) inside customeCellClass.
- (void)setFrame:(CGRect)frame { // method to insert gap between table view cell
frame.origin.y += 6;
frame.size.height -= 2 * 6;
[super setFrame:frame];
}
You can not really change the frame of the inbuilt subviews of uitableviewcell like imageview, accessoryview. But if you create a custom tableviewcell class(even if you do not add any other subelement to it), you can change the frame of the inbuilt imageview by overriding the layoutSubviews method inside the UITableViewCell. I have tried it and it works.
#import "TableViewCell.h"
#implementation TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(void) layoutSubviews{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame.origin.x = 0;
self.imageView.frame = frame;
}
#end
[Update] Ive added some content inset to my table view. So is it possible to add a view in the gap that is created ?
What I want to achieve is the following scroll effect :
Open the AppStore on the iPhone and go to the Featured tab.
Swipe down : Only the table view scrolls down, the banner with images stays fixed
Swipe up : Everything including the banner view moves up.
My initial thoughts were that there is a table view inside a scroll view but Apple advises against doing that ...
My second attempt was to use a table header view, but the header view would scroll both ways.
So what kind of tweaking has to be done to UITableViewController to achieve this effect ?
What I have so far :
What I need is basically this
For example :
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y < 0 ){
CGRect frame = self.myView.frame;
frame.origin.y = scrollView.contentOffset.y ;
self.myView.frame = frame;
}
}
It's the idea !
They used sections nothing fancy.
You'll want to use:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
Try returning a UIView for the viewForHeaderInSection and it will give you the same as the App Store Features tab.
Playing around with prototype cells and I need to add footer view at the bottom of table view, so a user could see this footer view when he would scroll to the bottom of the table view. So, created demo project with one screen, table view and two prototype cells. Looking for a way how to drag and drop some view below the table using Interface Builder. The problem is it looks like view is put outside table view content, so I see the footer but I can't scroll to it (only a small part of footer view is seen at the bottom of the table view).
I know this should work because already saw a working implementation but cannot figure out what magic setting or code line I need to add.
Here are the methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
return 2;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat rowHeight = 10;
switch (indexPath.row) {
case 0: {
rowHeight = 376;
break;
}
case 1: {
rowHeight = 105;
break;
}
}
return rowHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0: {
Cell1 *cell1 = [tableView dequeueReusableCellWithIdentifier:#"Cell1" forIndexPath:indexPath];
return cell1;
break;
}
case 1: {
Cell2 *cell2 = [tableView dequeueReusableCellWithIdentifier:#"Cell2" forIndexPath:indexPath];
return cell2;
break;
}
}
return nil;
}
Just making 2 row table with two different height cells. Cell1 and Cell2 classes are empty subclasses of UITableViewCell.
Here's how table view and cells look in interface builder:
Here's initial view after launch:
Here's what I see if I scroll to the bottom:
The footer is there, but outside table view (scroll content). As you can see, table view by default reserves some 44px space at the bottom for footer. But if I set footer height in tableView:heightForFooterInSection: then blank spaces appear.
Also, tried to drag and move this view up to view hierarchy in IB, so the view would become a header view. In that case, the view is shown at the top as header view, but then the second cell is shown only partially when scrolling to the bottom. It looks like table calculates how much space it needs to show prototype dynamic cells (have set "Dynamic Prototypes" for the table view). And if you add extra footer or header view to the interface builder then there's less space for the cells (if view is added as header) or the footer is not shown.
UPDATE. If I add this method then blank spaces appear:
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 50;
}
Here's what I get in that case (blank spaces below cell2):
UPDATE2 Footer is shown correctly if I disable autolayout.
I think you have missed the following line
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return HEIGHT_OF_YOUR_FOOTER_VIEW;
}
Edit
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
UIView *footer=[[UIView alloc] initWithFrame:CGRectMake(0,0,320.0,50.0)];
footer.layer.backgroundColor = [UIColor orangeColor].CGColor;
return footer;
}
Updated
Check this documentation
At last found the solution. Have noticed footer is shown correctly when autolayout is disabled, so started to look at constraints. So, have added leading space to container, trailing space, bottom space and top space to container constraints to table view using Ctrl + Drag. However IB showed red warning about missing Y position constraint. So, after choosing "Add missing constraints" from IB suggestion panel, IB added another system constraint but the footer was still not show correctly. It appears IB was unable to add correct top space and bottom space to container system constraints, and even to fix that. So I'v got 5 system constraints as a result of that:
Adding system constraints using Ctrl + Drag from table view to containing view have worked in previous demos for me. However, this time IB was unable to add correct top space and bottom space to container, and so top space vertical constraint had a value of -568. Tried setting to 0 but it didn't worked. Tried ten times to delete all constraints and add them again. The same result.
So, I deleted all these vertical (bottom and space) constraints and then selected "Add missing constraints". And bingo! IB added correct vertical constraints and the footer view was shown correctly. Here's how correct constraints should look like. However, I still don't understand why IB was unable to add correct constraints when I was doing Ctrl + Drag from table view to container view.
I have an app with 2 layers (at the moment); bottomlayer has a tableView, and I would like top layer to show different buttons/labels/etc based on tableView selection. Instinct to to hide/show layers based on didSelectRowAtIndexPath, but that would necessitate hiding/showing a lot of layers based on selection. I also imagine the storyboard would get quite messy as well :(
Is there a more reasonable/effective method for changing the UIView on the toplayer based on tableview selection?
Loop through them all in a for-in loop kind of like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
for (UIView *view in self.view.subviews) //change this to your container {
if (view.tag == 14)
continue;
/*Do Nothing, this is the golden view that stays on screen. Maybe set it's frame or alpha, but don't hide it.
*/
else
[view setFrame:CGRectMake(0, self.view.bounds.size.height, 768, 1024)]; //set this to any value that puts the view offscreen or set alpha to 0
}
}
}
it all depends on you setting their tags, which index is selected and which view you want to exclude.
Although, depending on the changes you want, or how many objects you have to change, this method may actually be more cumbersome.