I have an application that uses more UIScrollViews and extensions from it like UICollectionViews and UITableViews and for a little dynamism I have created some general functions to help me with some paddings / margins.
My functions are created as extensions for UIScrollView and I want to use them just when the scroll view is only vertical.
My Question
How can I find the scrolling direction of a UIScrollView?
I have found a lot of answers of how to find the scrolling direction, but I need when the UIScrollView is initialised to find the scrolling direction of it, if it's vertical or horizontal.
Thank you for your time!
One solution I can think of in order to get the scroll direction before scrolling is to react accordingly to the type of UIScrollView in question.
So first step check if it is a UIScrollView, UICollectionView or UITableView.
In the case of a UITableView
I think a fair assumption here is that the scrolling direction will be vertical
In the case of a UIScrollView
This one could be tricky but I guess here you would have to compare the content size of the scroll view with the frame of the scrollview as Desdenova suggested and this can suggest if you can scroll horizontally and / or vertically.
In the case of a UICollectionView
This data will lie in the in the layout of the UICollectionView.
For example, I set up a basic UICollectionView in Storyboard using a FlowLayout without any data in it.
Then I add this code somewhere appropriate
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
{
if layout.scrollDirection == .horizontal {
print("horizontal")
}
else {
print("vertical")
}
}
This prints out horizontal.
For the UIScrollView you have to use the UIScrollViewDelegate scrollViewDidScroll which is helps you in UIScrollView's Scroll direction also for that you have to add some checks for the get the directions:
func scrollViewDidScroll(scrollView: UIScrollView!) {
// Update ScrollView direction
if self.lastContentOffset.y > scrollView.contentOffset.y && self.lastContentOffset.y < scrollView.contentOffset.y {
// Vertical Scroll
} else {
// Horizontal Scroll
}
}
Related
How to find the scroll difference of a UITableView.
I need to find the difference of the Tableview content scroll from the tableview top position in swift
I tried the below code,
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = tableview.frame.origin.y
let maximumOffset = (contentOffset - scrollView.contentOffset.y)
let dif = maximumOffset
}
Contents offset of tableview, itself describe as difference between top of content to current scroll position.
Try this and see:
print(tableView.contentOffset)
For more, see Apple Document - contentOffset
contentOffset : The point at which the origin of the content view is offset from the origin of the scroll view.
Also look at this refernce:
Understanding of Content Offset
A UITableView is a subclass of UIScrollView, so it has a contentOffset property.
When you're learning about a class, remember to check it's parent class. Sometimes the properties/methods you're looking for are in the parent. (I'm as guilty of forgetting to check the interface of parent classes as anybody.)
I have a scrollview contains all views on the viewcontroller including a tableview. all views are scrolled like this :
but I want that the tableview stay at buttom of page and just increase its height like this :
I changed all constrains and googled but I couldnot find solution to this.How and Where I can increase table height when scrollview scrolls?
You can increase table size with respect to scrollView contantSize in viewDidLayoutSubView() method
Provide a fixed height constraint to UITableView in storyboard or xib. Make an outlet for that constraint
override func viewDidLayoutSubviews() {
super .viewDidLayoutSubviews()
self.constraintTableViewHeight.constant = self.scrollView.ContentSize.height
}
Hope it help. Happy codding:)
I'll get right to the point.
I have a UIViewController that has two subviews in it. The top one (let's call it HeaderView from now one) is a custom UIView and the bottom one is a UITableView.
I have set them up in InterfaceBuilder so that the HeaderView has 0 margin from the left, top and right, plus it has a fixed height.
The UITableView is directly underneath with 0 margin from all sides.
My goal is to achieve a behaviour such that when I start scrolling the UITableView's content the HeaderView will start shrinking and the UITableView becomes higher without scrolling. This should go on until the HeaderView has reached a minimum height. After that the UITableView should start scrolling as normal. When scrolling down the effect should be reversed.
I have initially started this out using a UIScrollView instead of the UITableView and I have achieved the desired result. Here is how:
connect the UIScrollView to the outlet
#IBOutlet weak var scrollView: UIScrollView!
set the UIScrollViewDelegate in the controller's viewDidLoad() method
self.scrollView.delegate = self
and declared the UIViewController to conform to the protocol
intercept when the UIScrollView scrolls:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.adjustScrolling(offset: scrollView.contentOffset.y, scrollView: scrollView)
}
in my adjustScrolling(offset:scrollView:) method the "magic" happens
Now let's look at what happens in this method.
private func adjustScrolling(offset: CGFloat, scrollView: UIScrollView) {
// bind value between 0 and max header scroll
let actualOffset: CGFloat = offset < 0 ? 0 : (offset >= self.maxHeaderScroll ? self.maxHeaderScroll : offset)
// avoid useless calculations
if (actualOffset == self.currentOffset) {
return
}
/**
* Apply the vertical scrolling to the header
*/
// Translate the header up to give more space to the scrollView
let headerTransform = CATransform3DTranslate(CATransform3DIdentity, 0, -(actualOffset), 0)
self.header.layer.transform = headerTransform
// Adjust header's subviews to new size
self.header.didScrollBy(actualOffset)
/**
* Apply the corrected vertical scrolling to the scrollView
*/
// Resize the scrollView to fill all empty space
let newScrollViewY = self.header.frame.origin.y + self.header.frame.height
scrollView.frame = CGRect(
x: 0,
y: newScrollViewY,
width: scrollView.frame.width,
height: scrollView.frame.height + (scrollView.frame.origin.y - newScrollViewY)
)
// Translate the scrollView's content view down to contrast scrolling
let scrollTransform = CATransform3DTranslate(CATransform3DIdentity, 0, (actualOffset), 0)
scrollView.subviews[0].layer.transform = scrollTransform
// Set bottom inset to show content hidden by translation
scrollView.contentInset = UIEdgeInsets(
top: 0,
left: 0,
bottom: actualOffset,
right: 0
)
self.currentOffset = actualOffset
}
If I haven't forgotten anything this should be enough to achieve the desired effect. Let me break it down:
I calculate the actualOffset binding it between 0 and self.MaxHeaderScroll which is just 67 (I think, it's calculated dynamically but this doesn't really matter)
If I see that the actualOffset hasn't changed since the last time this function was called I don't bother to aplly any changes. This avoids some useless calculations.
I apply the scrolling to the header by translating it up with a CATransform3DTranslate on just the y axis by negative actualOffset.
I call self.header.didScrollBy(actualOffset) so that the HeaderView can apply some visual changes internally. This doesn't concearn the question though.
I resize the scrollView so that it keeps 0 margin from top and bottom now that the HeaderView is higher up.
I translate down the scrollView's content by the same actualOffset amount to contrast the scrolling. This piece is essential to the correct visual effect that I want to achieve. If I didn't do this, the scrollView would still resize correctly but the content would start scrolling right away, which I don't want. It should only start scrolling once the HeaderView reaches it's minimum height.
I now set a bottom inset in the scrollView so that I am able to scroll it all the way to the end. Without this, the last part of the scrollView would be cut off since the scrollView itself would think it reached the end of it's content.
Lastly I store the actualOffset for later comparison
As I said, this works fine. The problem arises when I switch from a UIScrollView to a UITableView. I assumed it would work since UITableView inherits from UIScrollView.
The only piece of code that doesn't work is the number 6. I don't really know what is going wrong so I will just list everything I have found out and/or noticed. Hopefully someone will be able to help me out.
in the case of the UIScrollView, in point 6, the scrollView.subviews[0] refers to a view that holds all the content inside it. When I change to UITableView this subview seems to be of the type UITableViewWrapperView which I could not find any documentation about, nor does XCode recognize it as a valid class. This is already frustrating.
if in point 6 I also give some translation on the x axis (let's say of 50) I can see an initial very quick translation that is immediately brought back to 0. This only happens when the UITableView starts scrolling, it doesn't go on while scrolling.
I have tried changing the frame of the subview in point 6 to achieve the desired result. Although the scrolling is correct, the top cells start disappearing as I scroll the UITableView. I thin this is because I am using dequeueReusableCell(withIdentifier:for:) to instatiate the cells and the UITableView thinks that the top cells aren't visible when they actually are. I wasn't able to work around this problem.
I have tried setting the self.tableView.tableHeaderView to a UIView of the actualOffset height to contrast scrolling but this gave a weird effect where the cells would not scroll correctly and when the UITableView was brought back to the initial position, there would be a gap on top. No clue about this either.
I know there's a lot here so please don't hesitate asking for more details. Thank you in advance.
I made something like this recently, so heres how I achieved it:
Make a UIView with a height constraint constant and link this to your view/VC, have you UITableview constrained to the VC's view full screen behind the UIView.
Now set your UITableViews contentInset top to the starting height of your 'headerView' now, in the scrollViewDidScroll you adjust the constant until the height of the header is at its minimum.
Here is a demo
If you just run it, the blue area is your 'header' and the colored rows are just any cell. You can autolayout whatever you want in the blue area and it should auto size and everything
I'm looking to implement something very similar to the iOS Twitter Profile page, as seen here:
(source: twimg.com)
Based on what I can see, they have a UIView at the top, and a UIScrollView covering the entire view with a UITableView within the UIScrollView.
This is a tutorial on replicating it, and can be seen here: http://www.thinkandbuild.it/implementing-the-twitter-ios-app-ui/
The issue I've run into is how to maintain the momentum from scrolling on the UIScrollView vs. the UITableView. With the Twitter Profile page, you can scroll in one smooth swipe and it will move the UIScrollView up (showing the UITableView more) and any 'momentum' that is still there will start scrolling the UITableView.
I assume this must be done within the scrollViewDidScroll and check for any offset left over after reaching the bottom of the UIScrollView.
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView == self.myScrollView {
var maxOffset = 25.0
let offset = scrollView.contentOffset.y
self.myScrollView.frame = CGRectMake(0, -min(offset, maxOffset), view.frame.width, view.frame.height)
if offset - maxOffset > 0 {
self.myTableView.setContentOffset(CGPointMake(0,offset-maxOffset), animated:true)
}
}
This kind of works, although it certainly isn't smooth and doesn't appear as though it's maintaining momentum.
I don't think twitter's profile uses two scrollviews. It uses one tableview and adjusts the scroll indicator dynamically with the scrollIndicatorOffsets property. If you look closely at the scroll indicator you can see it shimmy a bit as you start to scroll up, which is consistent with this approach.
My pure AutoLayout UITableViewCell looks like this in Interface Builder:
UITableViewCell
|-> UITableViewCell.contentView
|-> UIView (ScrollViewContainerView)
|-> UIScrollView
|-> left (fixed)
|-> center (fill remaining)
|-> right (fixed)
The UIScrollView contains a left, center, and right UIView. left and right are both fixed width, while center expands to fill the remainder of the UIView. The UIScrollView constraints are to align all edges to ScrollViewContainerView. ScrollViewContainerView constraints are to align all edges to the UITableViewCell.contentView. I have a constraint on center's width to be a multiple of ScrollViewContainerView's width, so the UIScrollView scrolls left and right, but the height is fixed and does not scroll. Note that the UIScrollView has been subclassed to include this code so that the UITableView can detect a tap on the cell to toggle selection.
The issue is that I currently can either scroll the UITableView containing these UITableViewCells up and down or I can scroll the UIScrollViews in the UITableViewCells left and right, not both.
When ScrollViewContainerView.userInteractionEnabled == YES, I can't scroll the UITableView up and down, but I can scroll the UIScrollView left and right. When ScrollViewContainerView.userInteractionEnabled == NO, I can scroll the UITableView up and down, but I can't scroll the UIScrollView left and right. userInteractionEnabled == YES on everything else in the above hierarchy.
I can get away with having ScrollViewContainerView as a sibling view to the UIScrollView (making the UIScrollView the direct descent of contentView -- can't get rid of this view completely, because I require it to get the dimensions for the UIScrollView frame). In that case, the opposite handling with userInteractionEnabled holds.
I know I've done this before in other projects before, but starting fresh again, I can't seem to figure out what step I'm missing. Currently using Xcode 6 6A215l targeting iOS 8, though I have reproduced the issue under Xcode 5 targeting iOS 7.
It sounds like the scrollview is causing your tableview to not allow userInteraction when being scrolled. I'm sure that if you called - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView in the UIScrollView delegate (not sure for iOS 8), but you could just do
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if(scrollView.dragging == YES) {
self.<scrollViewName>.userInteractionEnabled = NO;
}
}
This is untested code, but it's just a bit of help to get you where you need to go.
Hope it helps!
I met some similar problem.
I have a scrollView in tableViewCell. All works fine.
Until one day, someone told me that the tableView can't scroll up/down when finger is touched on the scrollView in 6p. Just in 6p, not in 5, 5s,or6.
This makes me almost crazy.
Finally, I set the scrollView's height smaller than the height in storyboard.
Biu ~ It works~~~
Still, I don't know why.
#user2277872's answer put me on the right track to look at the output of the UIScrollView delegate methods of the UIScrollView in my UITableViewCell subclass. Putting an NSLog() in scrollViewWillBeginDragging: made me notice that the UIScrollView was receiving scrolling events while I was trying to scroll the UITableView. My UIScrollView had a contentSize larger than its frame in both directions, but I've forced that view to only scroll horizontal, so ignored the height and reset it. That force was my undoing and I should have known it at the time -- the correct solution is to fix the frame height. If the UIScrollView doesn't think there is more vertical content, it will correctly forward the swipe up/down gesture to the UITableView.
While I attempt to figure out why my contentSize is too large when it wasn't before (thinking I'm missing a clipToBounds somewhere), what I'm doing to force horizontal scrolling temporarily is (in the UITableViewCell's subclass):
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGSize contentSize = self.scrollView.contentSize;
contentSize.height = self.frame.size.height;
self.scrollView.contentSize = contentSize;
}
EDIT: Actually, this is seemingly better than overriding drawRect. This would be in the UIScrollView subclass:
/*
* Lock to horizontal scrolling only.
*/
- (void)setContentSize:(CGSize)contentSize
{
[super setContentSize:CGSizeMake(contentSize.width, 1)];
}
The height struct member isn't too important, as long as it's guaranteed to be smaller than the frame.size.height of the UITableViewCell. Still hacky, still need to find why I could clip before and not now.