The idea is replacing PageController with UIScrollView, UIScrollView should only scroll to the next/previous item and stop. I've found a few solutions, but I've only understood that I've to use targetContentOffsetForProposedContentOffset, but have no idea what CGPoint should I return and how to stop on the next item even if user scrolls with high velocity.
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
return CGPoint(x: 0, y: 0)
}
Use below of code. Your problem will be solved.
YourScrollView.pagingEnabled = YES;
With UIScrollView, you can enable paging. The property is called pagingEnabled. This means if it will snap to full widths (or heights) providing you set the content size properly.
i.e. If you want 2 pages and have a width of 100 and height of 200. You set the content size to be CGSize 200 for width and 200 for height. The scroll view will handle the snapping and paging.
If you want to have a function to go to a page like goToPage(2). Then you just need to call scrollRectToVisible method in UIScrollView with a CGRect of (pageNumber * scrollView.width (or page width), 1 height)
It looks like you are using Swift, so your implementation may vary.
Related
I have horizontal UIScrollView with isPagingEnabled = true containing few pages. If I drag scrollview content and release finger it scrolls to next page only if I dragged at least 50% of scrollview width. I want to auto scroll to next/previous page if drag distance is more than 25%.
I was able to achieve it by overriding scrollViewDidEndDragging in UIScrollViewDelegate, calculating drag distance and calling scrollView.setContentOffset. But issue is that if distance > 25 and < 50 then it scrolls back automatically probably because scrollview calls it's default implementation.
Any idea how can I achieve this? Thanks
I would use the following delegate's callback by modifying offset to desired page
// called on finger up if the user dragged. velocity is in points/millisecond.
// targetContentOffset may be changed to adjust where the scroll view comes to rest
#available(iOS 5.0, *)
optional func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>)
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 trying to get a UIScrollView to scroll upwards instead of downwards.I have everything set up except I don't know what line I should use to accomplish this. What should I do?
Niall
What I think you mean by this is that you want a scrollView that starts at the bottom and your content can be accessed by scrolling up.
In this case you simply need to figure out how large your content is, and set the setContentOffset of your scrollView to the height of your content:
override func viewDidLoad() {
// other code here...
// set the contentSize of the scrollView somewhere in here...
// get the offset based on the size of the content
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
// do not animate because we want to immediately scroll to the bottom
scrollView.setContentOffset(bottomOffset, animated: false)
This code is adapted from this answer.
I set the content size so vertical scrolling becomes activated but I only want the user to be able to scroll north and not south. Any ideas on how I can accomplish this?
//set high greater than view
myScroll.contentSize = CGSize(width: myView.view.frame.width,height: myView.view.frame.height + 190)
I only want the ability to scroll up and disable the ability to scroll down which is the default direction of the scrollview
You can set the content offset of the scroll view to the bottom. i.e.,
myScroll.contentOffset = CGPoint(x: 0,y: 190) // Here 190 is the same value that represent the height increase done in contentSize.
Make a subclass of UIScrollView. Override layoutSubviews to make sure contentOffset.y only increases, except when it's beyond the end of the content range, so the scroll view can bounce at the bottom.
class MyScrollView: UIScrollView {
var priorOffset = CGPoint.zero
override func layoutSubviews() {
var offset = contentOffset
if offset.y < priorOffset.y {
let yMax = contentSize.height - bounds.height
if offset.y < yMax {
offset.y = priorOffset.y
contentOffset = offset
}
}
priorOffset = offset
super.layoutSubviews()
}
}
Result:
select your Scrollview
select identity inspector
set user define attributes (see image)
in image first 435 vertical scrolling & second 116 is horizontal scroll
Note : set your own scrolling
You can set the direction of UIPanGestureRecognizer which is attached to your UIScrollView.
Check out some popular question which has an up-to-date answer, for example this one.
Or just go with pod 'UIPanGestureRecognizerDirection'
I would like to know how to implement parallax scrolling similar to Yahoo News Digest app. In which when user scroll horizontally background image scrolls in a different speed with the paging is enabled.
May be they do it with a ScrollView with a background view. Not exactly sure. Hint to implement such scrolling would be great. I have checked similar questions but couldn't find the answer I was looking for.
I've done this before with 2 scrollviews.
You have the main detail scroll view and then the parallax scroll view behind it (or wherever you want it).
Then you become the delegate of the detail scrollview.
In the method scrollView:didScroll you can then adjust the scroll of the parallax view.
If you're just doing the x axis then you want something like this...
CGFloat detailMaxOffset = self.detailScrollView.contentSize.width - CGRectGetWidth(self.scrollView.frame);
CGFloat percentage = self.detailScrollView.contentOffset.x / maxOffset;
CGFloat parallaxMaxOffset = self.parallaxScrollView.contentSize.width - CGRectGetWidth(self.parallaxScrollView.frame);
[self.parallaxScrollView setContentOffset:CGPointMake(percentage * parallaxOffset, 0);
This will set the scrollviews content offset "percentage" to be the same on each.
To get the parallax effect you just need to make the contentSize of each scrollview different.
If the parallax scroll view has a bigger content size than the detail scroll view it will scroll faster. If it has a smaller content size it will scroll slower.
Here is the answer. I subclass it from uitableview so that data can be reusable and wrap it in a uiview. https://github.com/michaelhenry/MHYahooParallaxView
Thanks,
Kel
100% working and dam easy
Take a view on imageview exactly size of image view.
by default alpha for view set 0.
//MARK: Scroll View Delegate methods
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"X: %f Y: %f",scrollView.contentOffset.x,scrollView.contentOffset.y);
CGFloat scrollY = _mainScrollView.contentOffset.y;
CGFloat height = _alphaView.frame.size.height;
CGFloat alphaMonitor = scrollY/height;
_alphaView.alpha = alphaMonitor;
}
Swift 3
Here's how I got a parallax effect to work in Swift 3 for a vertical scroll in a tvOS app.
In ViewDidLoad():
parallaxScrollView.delegate = self
detailScrollView.delegate = self
And following:
override func viewDidLayoutSubviews() {
self.detailScrollView!.contentSize = CGSize(width: 1920, height: 2700)
self.parallaxScrollView?.contentSize = CGSize(width: 1920, height: 3000)
}
//// THE SCROLL VIEW
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Parallax effect
let detailMaxOffset = self.detailScrollView.contentSize.height - self.detailScrollView.frame.height;
let percentage = self.detailScrollView.contentOffset.y / detailMaxOffset
let parallaxMaxOffset = self.parallaxScrollView.contentSize.height - self.parallaxScrollView.frame.height;
let parallaxOffset = CGPoint(x:0,y:(percentage * parallaxMaxOffset))
self.parallaxScrollView.setContentOffset((parallaxOffset), animated: false)
}