UIScrollView within a UIScrollView, how to keep a smooth transition when scrolling between the two
Yeah. Ive got a nifty trick for you.
Instead of having your red outlined view a scrollview make it a normal UIView that fills the screen. In that view lay out your scroll view (table view) and image view as they are in your illustration.
Place a scrollview that fills the bounds of the root view (i.e. also fills the screen) above all the other scrollview and image views. Set the content size of this view to be the total content height of all the views you want to scroll through. In otherwords there is an invisible scrollview sitting on top of all your other views and its content size height is inner scrollview (tableview) content size height + image view size height.
The heierarchy should look like this:
Then this scrollview on top that you have made with the really tall content size make its delegate be your view controller. Implement scrollViewDidScroll
and we'll work some magic.
Scrollviews basically scroll by adjusting the bounds origin with funky formulas for momentum and stuff. So in our scrollviewDidScroll
method we will simply adjust the bounds of the underlying views:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//the scroll view underneath (in the container view) will have a max content offset equal to the content height
//but minus the bounds height
CGFloat maxYOffsetForUnderScrollView = self.underScrollView.contentSize.height - self.underScrollView.bounds.size.height;
CGRect scrolledBoundsForContainerView = self.view.bounds;
if (scrollView.contentOffset.y <= maxYOffsetForUnderScrollView) {
//in this scenario we are still within the content for the underScrollView
//so we make sure the container view is scrolled to the top and set the offset for the contained scrollview
self.containerView.bounds = scrolledBoundsForContainerView;
self.underScrollview.contentOffset = scrollView.contentOffset;
return;
}
//in this scenario we have scrolled throug the entirety of the contained scrollview
//set its offset to the max and change the bounds of the container view to scroll everything else.
self.underScrollView.contentOffset = CGPointMake(0, maxYOffsetForUnderScrollView);
scrolledBoundsForContainerView.origin.y = scrollView.contentOffset.y - maxYOffsetForUnderScrollView;
self.containerView.bounds = scrolledBoundsForContainerView;
}
You will find that as scrollViewDidScroll is called every frame of animation that this faux scrolling of the contained views looks really natural.
But wait! I hear you say. That scroll view on top now intercepts ALL touches, and the views underneath it need to be touched as well. I have an interesting solution for that as well.
Set the scrollview on top to be off screen somewhere (i.e. set its frame off screen, but still the same size.) and then in your viewDidLoad
method you add the scrollview's panGestureRecogniser
to the main view. This will mean that you get all the iOS natural scrolling momentum and stuff without actually having the view on the screen. The contained scroll view will now probably go juddery as its pan gesture recognizer will get called as well (they work differently to UIEvent handling) so you will need to remove it.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addGestureRecognizer:self.scrollview.panGestureRecognizer];
[self.underScrollview removeGestureRecognizer:self.underScrollView.panGestureRecognizer];
//further code to set up content sizes and stuff
}
I had fun making this so heres a link to the sample project on github:
https://github.com/joelparsons/multipleScrollers
EDIT:
To show the scrollbar for the top scrollview when its off the screen no matter where you put it you can set the scrollIndicatorInsets
to an inset created like this:
CGPoint scrollviewOrigin = self.scrollview.frame.origin;
self.scrollview.scrollIndicatorInsets = UIEdgeInsetsMake(-scrollviewOrigin.y,0,scrollviewOrigin.y,scrollviewOrigin.x);
*caveat that the scrollview still has to be the right height but I'm sure you get the idea.
And then to make the bar draw outside the scrollview's visible bounds you have to turn off clips to bounds
self.scrollview.clipsToBounds = NO;
Two simultaneous scroll views behaving weirdly
I finally discovered the answer.
Basically, contentOffset = 0
is a magic number: at this value, the scroll view needs to pick up its velocity from 0 even if the gesture already has a velocity. Therefore, simply set the default offset to something like 1 will do:
private func childScrollViewDidScroll(_ scrollView: UIScrollView) {
if self.childScrollViewLocked {
scrollView.contentOffset.y = 1 // <- set it to 1 instead
} else if scrollView.contentOffset.y <= 1 {
self.childScrollViewLocked = true
self.parentScrollViewLocked = false
}
}
For perfectionists, set a corresponding content inset can offset this artificial offset (whelp!).
Now the two scroll views can hand off scrolling smoothly.
Smooth move of the contentoffset UIScrollView Swift
You can use UIView.animations
func goToPoint() {
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
}
Swift version
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.myScrollView.contentOffset.x = CGFloat(startingPointForView)
}, completion: nil)
}
Swift 4
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
Make transition between two Menus with UIScrollView
Are you avoiding UIPageViewController because of the complexity? Because if you are there is a much easier way to accomplish the same gesture controlled transition using UIScrollView's pagingEnabled property
Based on what I see in the screenshots...
var size = CGSize()
size.height = self.view.frame.height
size.width = self.view.frame.width * 2
let scrollView = UIScrollView()
scrollView.frame = self.view.frame
scrollView.contentSize = size
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
nav.center.y = scrollView.frame.height / 2
nav.center.x = scrollView.frame.width / 2
nav2.center.y = scrollView.frame.height / 2
nav2.center.x = nav.center.x + scrollView.frame.width
scrollView.addSubview(nav1)
scrollView.addSubview(nav2)
self.view.addSubview(scrollView)
Related Topics
Using Os_Log to Log Function Arguments, or Other Dynamic Data
How to Bind a Variable to Multiple Alternatives in a Switch Statement
The File "Xxx.Mp4" Couldn't Be Opened Because You Don't Have Permission to View It
Scenekit Some Textures Have a Red Hue
How to Build a Workout App on Watchos with Audio Feedback
Aws S3 Transfer Manager ${Cognito-Identity.Amazonaws.Com:Sub} Policy Variable Access Denied
How to Use Storyboards with Spritekit Using Swift
Swift: Extract Float from Byte Data
Storyboard Tableview with Segues to Multiple Views
How to Detect That Parameter Is a Tuple of Two Arbitrary Types
How to Create String Split Extension with Regex in Swift
Navigationview Doesn't Display Correctly When Using Tabview in Swiftui
Calling Getsectiondata from Swift
How to Resolve This Build Issue - Cannot Assign to Property: 'Date' Is a Get Only Property
How to Create a Static Class in Swift
Scene Created in Sprite Kit Level Editor Is Not Working
Getting Unresolved Identifier 'Self' in Swiftui Code Trying to Use Timer.Scheduledtimer