可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a UIView
with a UITableView
below it:
What I would like to do is to have the view above the UITableView
move up (out of the way) when the user starts scrolling in the table in order to have more space for the UITableView
(and come down when you scroll down again).
I know that this is normally done with a table header view, but my problem is that my table view is inside a tab (actually it is a side-scrolling page view implemented using TTSliddingPageviewcontroller
). So while I only have one top UIView
there are three UITableView
s.
Is it possible to accomplish this manually? My first thought is to put everything in a UIScrollView
, but according to Apple's documentation one should never place a UITableView
inside a UIScrollView
as this leads to unpredictable behavior.
回答1:
Since UITableView
is a subclass of UIScrollView
, your table view's delegate can receive UIScrollViewDelegate
methods.
In your table view's delegate:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
static CGFloat previousOffset;
CGRect rect = self.view.frame;
rect.origin.y += previousOffset - scrollView.contentOffset.y;
previousOffset = scrollView.contentOffset.y;
self.view.frame = rect;
}
回答2:
Solution for Swift (Works perfectly with bounce enabled for scroll view):
var oldContentOffset = CGPointZero
let topConstraintRange = (CGFloat(120)..<CGFloat(300))
func scrollViewDidScroll(scrollView: UIScrollView) {
let delta = scrollView.contentOffset.y - oldContentOffset.y
//we compress the top view
if delta > 0 && topConstraint.constant > topConstraintRange.start && scrollView.contentOffset.y > 0 {
topConstraint.constant -= delta
scrollView.contentOffset.y -= delta
}
//we expand the top view
if delta < 0 && topConstraint.constant < topConstraintRange.end && scrollView.contentOffset.y < 0{
topConstraint.constant -= delta
scrollView.contentOffset.y -= delta
}
oldContentOffset = scrollView.contentOffset
}
回答3:
Swift 3 & 4:
var oldContentOffset = CGPoint.zero
let topConstraintRange = (CGFloat(0)..<CGFloat(140))
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let delta = scrollView.contentOffset.y - oldContentOffset.y
//we compress the top view
if delta > 0 && yourConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
yourConstraint.constant -= delta
scrollView.contentOffset.y -= delta
}
//we expand the top view
if delta < 0 && yourConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0{
yourConstraint.constant -= delta
scrollView.contentOffset.y -= delta
}
oldContentOffset = scrollView.contentOffset
}
回答4:
More simple and fast approach
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect rect = self.view.frame;
rect.origin.y = -scrollView.contentOffset.y;
self.view.frame = rect;
}
回答5:
Someone asked for the code for my solution so I am posting it here as an answer. The credit for the idea should still go to NobodyNada.
In my UITableViewController
I implement this delegate method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[[NSNotificationCenter defaultCenter] postNotificationName:@"TableViewScrolled" object:nil userInfo:scrollUserInfo];
}
scrollUserInfo
is a NSDictionary
where I put my UITableView
to pass it with the notification (I do this in viewDidLoad
so I only have to do it once):
scrollUserInfo = [NSDictionary dictionaryWithObject:self.tableView forKey:@"scrollView"];
Now, in the view controller that has the view I want to move off screen while scrolling I do this in viewDidLoad
:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleScroll:) name:@"TableViewScrolled" object:nil];
And finally I have the method:
- (void)handleScroll:(NSNotification *)notification {
UIScrollView *scrollView = [notification.userInfo valueForKey:@"scrollView"];
CGFloat currentOffset = scrollView.contentOffset.y;
CGFloat height = scrollView.frame.size.height;
CGFloat distanceFromBottom = scrollView.contentSize.height - currentOffset;
if (previousOffset < currentOffset && distanceFromBottom > height) {
if (currentOffset > viewHeight)
currentOffset = viewHeight;
self.topVerticalConstraint.constant += previousOffset - currentOffset;
previousOffset = currentOffset;
}
else {
if (previousOffset > currentOffset) {
if (currentOffset < 0)
currentOffset = 0;
self.topVerticalConstraint.constant += previousOffset - currentOffset;
previousOffset = currentOffset;
}
}
}
previousOffset
is an instance variable CGFloat previousOffset;
.
topVerticalConstraint
is a NSLayoutConstraint
that is set as a IBOutlet
. It goes from the top of the view to the top of its superview and the initial value is 0.
It's not perfect. For instance, if the user scrolls very aggressively up the movement of the view can get a bit jerky. The issue is worse for large views; if the view is small enough there is no problem.
回答6:
I added some constraints to the last solution to prevent some strange behaviours in case of fast scrolling
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let delta = scrollView.contentOffset.y - oldContentOffset.y
//we compress the top view
if delta > 0 && topConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
searchHeaderTopConstraint.constant = max(topConstraintRange.lowerBound, topConstraint.constant - delta)
scrollView.contentOffset.y -= delta
}
//we expand the top view
if delta < 0 && topConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0 {
topConstraint.constant = min(searchHeaderTopConstraint.constant - delta, topConstraintRange.upperBound)
scrollView.contentOffset.y -= delta
}
oldContentOffset = scrollView.contentOffset
}
回答7:
To create like this animation,
lazy var redView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width:
self.view.frame.width, height: 100))
view.backgroundColor = .red
return view
}()
var pageMenu: CAPSPageMenu?
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(redView)
let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
pageMenu?.view.frame = rect
self.view.addSubview(pageMenu!.view)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if(offset > 100){
self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 0)
}else{
self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 100 - offset)
}
let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
pageMenu?.view.frame = rect
}
you must change pageMenu.view with your collectionView/tableView
回答8:
I know this post in very old. I tried above solutions but neither worked for me for tried my own, hopefully it can help you. This scenario is pretty common, as apple suggested not to use TableViewController inside any ScrollView because the compiler will confused as in whom to respond becuase it will be getting two delegate call back - one from ScrollViewDelegate and another from UITableViewDelegate.
Instead we can use ScrollViewDelegate and disable the UITableViewScrolling.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat currentOffSetY = scrollView.contentOffset.y;
CGFloat diffOffset = self.lastContentOffset - currentOffSetY;
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 400 + [self.tableView contentSize].height);
if (self.lastContentOffset < scrollView.contentOffset.y) {
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y , tableView.frame.size.width, tableView.size.height - diffOffset);
}
if (self.lastContentOffset > scrollView.contentOffset.y) {
tableView.frame = CGRectMake(tableView.frame.origin.x, tableViewframe.origin.y, tableViewframe.size.width, tableView.frame.size.height + diffOffset);
}
self.lastContentOffset = currentOffSetY;
}
Here lastContentOffset is CGFloat defined as property
The View Heirarchy is as follows:
ViewController --> View contains ScrollView (whose delegate method is defined above) --> Contain TableView.
By the above code we are manually increasing and decreasing the height of the table view along with the content size of ScrollView.
Remember to disable the Scrolling of TableView.