I'm trying to replicate the following GIF from Postmates checkout - a scrollable UITableView positioned on top of a MapView. This tableview can be scrolled, with the normal bounce effect if I go too far down or up.
Currently, I have the MapView and UITableView added as sibling views to my ViewController's view. I have adjusted the frame of the table view to move it down.
CGRect rect = CGRectMake(
0.f,
200.f,
self.view.bounds.size.width,
self.view.bounds.size.height - self.navigationController.navigationBar.bounds.size.height - 200.f
);
The two main issue's I'm having are:
I cannot figure out how to drag the entire tableview down when pulling down. E.g. the grey (my tableView.backgroundColor) sticks when I scroll down. If however, I make that background clear, then when I drag up, you see the map emerging from behind the view.
My cells keep disappearing when I scroll up. I have clipsToBounds = false, and I'm not actually dequeuing cells, just creating them in my cellForRow method, but they still disappear.
I feel like this should be a straightforward layout, but I'm missing something!
I've tried adjusting the contentInset of the table view, but then the scrollbar does not align with the cells as it does in the gif and does not look nice.
We needed similar effect in our app, along with parallax in the underlying view(map here/ we had a photos gallery).
I assume you want something like shown on this blogs video here
I wrote a small blog on how to achieve this. You can find this here
Basically its just a play of contentInsets and contentOffset properties of UITableView
If this does not suits you, here my suggestion in your two main points.
Controller's View has subviews
Map View (fills complete super view)
UITableView (fills complete super view) and in code set content insets.top = kHeightOfVisibleMap
The solution was simpler than I thought, no autolayout or crazy weird tricks required.
Add a full screen table view to your controller, and insert a full screen map view behind it.
self.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
[self.view insertSubview:self.mapView belowSubview:self.tableView];
Add a dummy view with the background color of your table view with a height of around 200 pixels, and a full width, into the tableFooterView on your table view.
CGFloat footerHeight = 200.0;
UIView *dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, footerHeight)];
dummyView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = dummyView;
Set the content offset of the table view to be the inverse of that footer view's height:
CGFloat footerHeight = dummyFooterView.bounds.height;
self.tableView.contentOffset = CGPointMake(0, -footerHeight);
Set the content inset of your table view to offset the footer view.
self.tableView.contentInset = UIEdgeInsetsMake(footerHeight, 0, -footerHeight, 0)
Adjust the scrollbar position, again, based on the footer's height.
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(footerHeight, 0, 0, 0);
This will push down the tableview, ensure the scroll bars match the table view, but allow it to bounce 'up' above its initial position and will ensure the background does not peek through the bottom of the tableview.
Related
I have created a tableview in code as follows:
_myTableView = [[UITableView alloc] initWithFrame:
CGRectMake(160, 80, 140, 100) style:UITableViewStylePlain];
_myTableView.delegate = self;
_myTableView.dataSource = self;
_myTableView.scrollEnabled = YES;
[self.view addSubview:_myTableView];
It largely works as it should with the following exception. Because the results in the table vary, I manually adjust the height of the tableview so that it only takes up as much space as the returned rows need as follows:
-(void) changeTVHeight: (float) height {
//height calculated from number of items in array returned.
CGRect newFrame = CGRectMake(120, 80, 180, height);
self.myTableView.frame = newFrame;
}
This works great for shrinking the tableview if there aren't that many results.
However, if there are a lot of results, the tableview expands below the visible part of the screen or the keyboard. In this case, I would like to be able to scroll the Tableview to see the lower rows.
scrollEnabled is set to YES.
But while it does allow one to scroll a bit, the scroll is resisted so with effort you can scroll a little bit but due to rubber band effect you cannot get further than a few rows below the screen and you cannot tap on the lower rows.
I am using autolayout in storyboard for much of the screen. The overall screen scrolls fine but this merely moves the tableview anchored to the screen up and down. There are no constraints on this tableview but it is added as a subview of the view.
My question is how can I make the tableview scrollable so that it scrolls without resistance?
Thanks in advance for any suggestions.
Edit:
I tried adding the tableView to self.scrollView instead of self.view. This anchored the tableView to the scrollview so it is possible to scroll the whole screen down and see the bottom of the tableview. However, this is not ideal because the rest of the screen is empty way down and you can't see the context for the tableview. (It's an autocomplete for a textfield at top of screen.)
In contrast when the tableview is added to self.view, it is in correct place, it semi-scrolls or bounces. It just doesn't scroll down to where I need it to scroll.
You need to set a limit so that the table view cannot be larger than the view itself. Tableviews are built on UIScrollView and will handle scrolling on their own, you don't need to try to size it manually. The reason the table view bounces but doesn't scroll is because it is extending below the bottom of the screen. It wont scroll because it has already scrolled to the bottom, you just can't see it because it's outside of the superview.
-(void) changeTVHeight: (float) height {
CGFloat limitedHeight = MIN(height, self.view.frame.size.height)
CGRect newFrame = CGRectMake(120, 80, 180, limitedHeight);
self.myTableView.frame = newFrame;
}
I'm trying to figure out what my options are for having the top of a UITableView be able to stay at rest about 1/3 down the screen but still be able to scroll over the top of an image above it.
I was thinking UITableView on a UIScrollView but I haven't seen this exact thing while googling around for it.
Basically I'm looking to have a header and then the table view will scroll over the top of the header without the header moving. If I set the table view below a simple uiview or imageview, it will always be below it.
Any suggestions?
Add your header as a separate view below your table view. Your table view should completely overlap the underlying view, as if it would obscure it. Then set the table view's tableHeaderView to a transparent UIView, through which you will be able to see your underlying header view. Your table view's background colour should also be clear.
CGRect headerFrame = self.underlyingHeaderView.bounds;
UIView *transparentView = [UIView alloc] initWithFrame:headerFrame];
transparentView.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = transparentView;
I have a UIScrollView which contains a UIView and a UITableView. My goal is to adjust the height of the UIScrollView to allow me to scroll the contents of the UIScrollView to a specific point.
Here is my view: It has a UIView up top and a UITableView down below.
When I scroll, I want the UIView to stop at a specific point like so:
The tableView would be able to continue scrolling, but the UIView would be locked in place until the user scrolled up and brought the UIView back to its original state.
A prime example of what I am trying to do is the AppStore.app on iOS 6. When you view the details of the app, the filter bar for Details, Reviews and Related moves to the top of the screen and stops. I hope this all made sense.
Thanks
I ended up going with a simpler approach. can't believe I didn't see this before. I created two views, one for the UITableView's tableHeaderView and one for the viewForHeaderInSection. The view I wanted to remain visible at all times is placed in the viewForHeaderInSection method and the other view is placed in the tableHeaderView property. This is a much simpler approach, I think than using a scrollview. The only issue I have run into with this approach is all my UIView animations in these two views no longer animate.
Here is my code.
[self.tableView setTableHeaderView:self.headerView];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.tableViewHeader;
}
add yourself as a UIScrollViewDelegate to the UITableView and implement the - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView so that if your views are in their starter positions they do this:
- your UITableView animates its size to the second state:
[UIView animateWithDuration:.1f animations:^{
CGRect theFrame = myView.frame;
theFrame.size.height += floatOfIncreasedHeight;
myView.frame = theFrame;
}];
- your UIView animates its vertical movement
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void){
view.center = CGPointMake(view.center.x , view.center.y + floatOfVerticalMovement);
}completion:^(BOOL Finished){
view.center = CGPointMake(view.center.x , view.center.y - floatOfVerticalMovement);]
Finally always in the delegate implement – scrollViewDidScrollToTop: so that you know can animate back to the initial state (using the same techniques reversed).
UPDATE:
since your views are inside a scroll view, there is a simpler way if you are ok with the table view being partly out of bounds in your starter position (i.e. instead of changing size it just scrolls into view):
make the scroll view frame size as big as your final tableview + your initial (entire) view and place it at 0,0 (so its final part will be hidden outside of the screen)
scrollview.frame = CGRectMake(0,0,tableview.frame.size.width,tableview.frame.size.height + view.frame.size.height);
you make the container scrollview contents as big as the entire table view + the entire view + the amount of the view that you want out of the way when scrolling the table view.
scrollview.contentSize = CGSizeMake(scrollview.frame.size.width, tableview.frame.size.height + view.frame.size.height + floatOfViewHeightIWantOutOfTheWay);
you place the view one after the other in the scrollview leaving all the additional empty space after the table view
view.frame = CGRectMake(0,0,view.frame.size.width, view.frame.size.height);
tableview.frame = CGRectMake(0,view.frame.size.height, tableview.frame.size.width, tableview.frame.size.height);
now it should just work because since iOS 3 nested scrolling is supported
You can easily achieve this by setting the content size of the scrollView correctly and keep the height of the UITableView smaller than your viewcontroller's height, so that it fits the bottom part of the top UIView and the UITableView...
Another scenario is to split the top View in 2 parts.
The part that will scroll away and the part that will be visible.
Then set the part that will scroll away as the entire UITableView header and the part that will remain visible as the header view for the first table section.
So then you can achieve this with a single UITableView, without having to use a UIScrollView
What you're looking for is something like what Game Center happens to do with it's header which can actually be modelled with a table header, a custom section header view, and some very clever calculations that never actually involve messing with the frame and bounds of the table.
First, the easy part: faking a sticky view. That "view that's always present when scrolling the table" implemented as a section header. By making the number of sections in the table 1, and implementing -headerViewForSection:, it's possible to seamlessly make the view scroll with the tableview all for free (API-wise that is):
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
label.text = #"Info that was always present when scrolling the UITableView";
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor colorWithRed:0.243 green:0.250 blue:0.253 alpha:1.000];
label.textColor = UIColor.whiteColor;
return label;
}
Finally, the hard part: KVO. When the table scrolls, we have to keep the header up there sticky with regards to the top of the view's frame, which means that you can KVO contentOffset, and use the resultant change in value to approximate the frame that the view should stick to with a little MIN() magic. Assuming your header is 44 pixels tall, the code below calculates the appropriate frame value:
CGPoint offset = [contentOffsetChange CGPointValue];
[self.tableView layoutSubviews];
self.tableView.tableHeaderView.frame = CGRectMake(0,MIN(0,offset.y),CGRectGetWidth(self.scrollView.frame),44);
If the above is infeasible, SMHeadedList actually has a fairly great, and little known, example of how complicated it can be to implement a "double tableview". That implementation has the added benefit of allowing the "header" tableview to scroll with the "main" tableview.
For future visitors, I've implemented a much simpler version, albeit one that accomplishes the goal with Reactive Cocoa, and a little bit of a different outcome. Even so, I believe it may be relevant.
What if you break the UIView into the top and bottom. The bottom will be the info.
Set UITableView.tableHeaderView = topView in viewDidLoad
and the return bottomView as Section Header in delegate method to make it float:
(UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
{
return bottomView;
}
Just using the UITableView can solve with your problem. it is not need to use another scroll view.
set your view as the header view of UITableView. Then add your present view to the header view.
complete - (void)scrollViewDidScroll:(UIScrollView *)scrollView; . Tn the function to check the contentoffset of scroll view, and set the present view's frame.
My UITableView has a subview with a fixed position. But when I scroll, the scrollbar is hidden by the subview. How can I avoid this?
## EDIT ##
self.menuViewRelative = [[UIView alloc] init];
self.menuViewRelative.backgroundColor = [UIColor whiteColor];
self.menuViewRelative.opaque = YES;
self.menuViewRelative.frame = CGRectMake(0.0, -1.0, 320.0, 50.0);
[self.view insertSubview:self.menuViewRelative atIndex:11];
This subview is now above 10 other subviews and exactly 1 layer under the scrollView (so it displays the scroll indicator perfectly).
Adding as an answer as per Jesse Rusak's comment.
Set scrollIndicatorInsets for the table view in such a way that the scroller will be moved in a place where subview wont overlap.
For eg:-
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 0, 7.0);
Are you using addSubview: for this? That will make it the top-most subview, which will cover the scroll bars. If you instead use insertSubview:atIndex: and ensure it's just above the other content in your table view, it will be below the scroll bars.
You might need to override layoutSubviews of the UITableView (like I describe in this related question) and look through the contents of the table view in order to keep your floating view just above the table view cells.
I have the CNN app on my iPhone.
If you open it will show "top stories", and has always a picture on the top of the table.
When we slide our finger down it expands/zooms the image.
When we slide our finger up to show more rows it does not move the image upwards at the same speed has the table rows instead the table rows move faster than the picture going off the screen.
Do you know how to do this effect?
You need UIImageView to UIScrollView contentInset and set top inset more than zero.
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
UIImage *topImage = [UIImage imageNamed:#"photo"];
topImage.frame = CGRectMake(0, -topImage.frame.size.height, topImage.frame.size.width, topImage.frame.size.height);
[scroll addSubview:topImage];
self.contentInset = UIEdgeInsetsMake(topImage.frame.size.height, 0, 0, 0);
After that set UIScrollView's contentOffset like this
scroll.contentOffset = CGPointMake(0, topImage.frame.size.height);
So you have the UIScrollView with image inside it. Now you just need to add delegate to UIScrollView and wait for - (void)scrollViewDidScroll:(UIScrollView *)scrollView. Resize image and change contentInset right after this method called.
This advice you also can apply to UITableView similar way, or create category on UIScrollView.
I also advice you to read code of this project https://github.com/samvermette/SVPullToRefresh. It's also about UITableView and adding view to it's top side
It seems like a completely custom effect that you will have to implement yourself. To point you in the right direction, I'd first monitor contentOffset changes in the scrollViewDidScroll: messages sent by your myTableView instance, and when the contentOffset.y < 0, change the scale of your imageView via
[myImageView setValue:[NSNumber numberWithFloat:scaleIncrementAmountFloat] forKeyPath:#"transform.scale"];
My guess is that the top cell (or cells) of the tableview are transparent and there is a UIImageView behind the tableview that is resized/moved based on how the table view is scrolled.
Since the table view inherits from UIScrollView, its delegate (UITableViewDelegate) inherits from the UIScrollViewDelegate protocol which is notified when a the user scrolls the scroll view. Implement the - (void)scrollViewDidScroll:(UIScrollView *)scrollView method to receive these notifications and check the contentOffset property on the scroll view.
You may have to implement you own table view controller to build the desired UI in the interface builder.