Triple Nested UIScrollView paging issue - ios

Backstory
I have an iPad app that needs to allow the user to navigate through groups of images. Each group is laid out in its own vertical UIScrollView (paged) so the user can swipe up and down down to see each image. Each of the group UIScrollViews is placed in a single (only one exists in the app) outer horizontal UIScrollView (also paged). This works great.... I can swipe up and down to view the images in a group and swipe left and right to go to the next or previous group.
Problem
The problem started when I needed to add zooming for each image. I accomplished this by placing each image inside its own UIScrollView. When the image is zoomed I can pan around the image and when I get to the top or the bottom of the zoomed image the group's vertical UIScrollView pages to the next or previous image as expected. Unfortunately the outer horizontal scrollview will not page to the next group when the image is zoomed and I pan to the leftmost or rightmost edge.
Is there a better(more correct) approach than triple nesting UIScrollViews or can I somehow forward touches to the outer horizontal scrollview?
Any help or suggestions would be greatly appreciated.

hope i'm not too late but I think I have a solution for your problem.
Here you can find an Xcode project demonstrating the scrollview setup you have, your problem and the proposed solution: https://bitbucket.org/reydan/threescrollviews
Basically the solution was to add 1 pixel to the contentSize.width of the vertical scrollviews. This forces the vertical scrollview to scroll a little when you pan to the edge of the zoomed image. It scrolls a little and then continues to the next vertical scrollview.
If you download the project you will see that I've created some scrollviews in the viewDidLoad method. There, I create one horizontal scrollview containing 3 vertical scrollviews, each containing 5 images. Each image is actually incapsulated in a scrollview to enable per-image zooming. In total... triple nested scrollviews.
I've also left some colored borders so that I can easily see how each scrollview scrolls.
the magenta = horizontal scrollview
the white = vertical scrollview
the blue = the image scrollview (the one that contains the image and allows for zooming)
the red = the UIImageView
You will see that I've tagged each image scrollview with value 10. This is used in the implementation of - (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView delegate method where I return nil unless the event came from one of the image scrollviews.
If you have any questions about the project I made feel free to ask.
In the end, I would like to say that this browsing method is a little quirky for me as I sometimes scroll in the unwanted direction. Often I think I flick my finger vertically only to find the scrollview going left or right because it interpreted some tiny horizontal movement I had.
The problem I found with paging enabled for both horizontal and vertical movement is that the scrollviews are direction-locked, or so it seemed to me.
EDIT:
Today I've investigated the problem even more. These are my conclusions:
it's not a problem with zooming, it's a problem with having larger content in the innermost scrollview than the visible area(you can try this by zooming or simply initializing the content size larger than the bounds). This enables panning inside the inner-most scrollview and completely changes the behaviour of the touch events.
the bounce for a scrollview flag affects the behaviour of the panning(dragging) gesture when it reaches the edges of the content. If bounces=false then your panning gesture will stop at the edge, not forwarding the drag event up the chain (and thus not scrolling the parent scrollviews to show you other images). If bounces=true then, when you reach the edge and continue to drag the events will be forwarded to the parent scrollview and that scrollview will also be dragged. However, I've found that the dragging while bouncing reduces the distance dragged by aproximately 50%. This also happens in the Photos app.
if you start the dragging while the innermost scrollview is at the edge of the content then the scrollview is smart and will forward all events to the parent scrollview.
for some reason, triple nested scrollviews are problematic as the events are simply not forwarded between the topmost and middle scrollviews while panning inside the innermost scrollview. I have no idea why.
My solution with that +1 pixel to the content size, partially solves the problem.
EDIT 2013
Boy, these scrollviews are something out of this world :(
After more than a year of searching (just kidding... it was actually 2 days) I think I found a good elegant solution to the triple nested scrollviews.
I created a test project here:
https://github.com/reydanro/TripleNestedScrollViews
Inside the app, there is a switch which you can use to test with/without the fix.
The setup I am using in my app is a little different than this question. I have 1 vertical paged scrollview. Inside it, I have multiple horizontal paged scrollviews.
Inside some of the horizontal scrollviews I have another vertical paged scrollview.
Without the fix, once you get to the page with the inner-most scrollview you are pretty much stuck there as the vertical scrolling gestures are not forwarded to the outer-most scroll.
The fix is a custom UIGestureRecognizer that you need to add to the inner-most scrollviews. This recognizer follows touch events and if it detects a drag beyond the contentArea, then it will temporarily disable the rest of the scrollview's recognizers. This is the only method I discovered to make the scrollview forward the events up the chain
The gesture recognizer code is very rough with limited customization but should get the job done. At the moment I am focused on the app I develop, but will continue to update the repository.
PS: I haven't tested what happens with zoom but I see no reason why this method should not work (or be adapted to work).

Related

XCUITests: Can't tap element on scrollview while views are stack on each other

Sample Project
This is a sample project that showing the issue. It's storyboard based, but method of building interface doesn't matter. It's UIViewController with UIScrollView for entire screen and 128 pts height view that is on top of this UIScrollView.
Inside scroll view there is an UIView that has 2000 pts height and UIButton in the center.
Initial State
After light scroll
At the bottom of UIScrollView
Link here: https://github.com/JakubMazur/UITestsDemo
Problem
I'm trying to tap this green button with XCUITest using app.buttons["Tap Me!"].tap()
XCUITest get identifiers from elements on screen for entire scroll view that works fine.
According to this reply on a thread on Apple Developer Forum written by Apple Framework Engineer I shouldn't scroll manually to get to the button and yes, this is partially true.
What is happening when code from (1) is executed is that button is scrolled just enough to be visible on screen but it's still not hittable, because other (purple view) is on top of UIScrollView
What is working
If I run a test written like this:
func testThatDoWorkButItsSlow() {
app.scrollViews.firstMatch.swipeUp()
app.buttons[buttonLabel].tap()
}
that is scrolling up and then looks for a button this will work, but it's slow and so inaccurate that is hardly usable.
What I cannot do
Disabling userInteractions on purple view. In real example I still need touches for this (purple) view.
Questions
Is there a way to use precise scrolling in XCTest for this case?
Or is there a way to set contentOffset scrollview to other value that will make this button more centered on a screen compared to action of tap()?
Or there is a way to fast scroll to the bottom (without animations) and maybe moving only up for each element?
My recommendation here would be to use the XCUICoordinate.press(forDuration:thenDragTo:) method to scroll.
https://developer.apple.com/documentation/xctest/xcuicoordinate/1615003-press
You can create a XCUICoordinate for the yellow view, then drag it slightly upwards to expose the button and make it hittable.
In most cases, the automatic scroll should work, but it seems like in this case a manual scroll/drag is necessary.
The UI Testing should replicate human interactions. You cannot expect from a human being to scroll "153px", you can just expect to "scroll until".
You can try something like :
while (!app.buttons["Tap Me!"].isHittable) {
app.swipeUp()
}
NB: You may also want to add a condition to leave the while loop if you can't find the button after a reasonable amount of attempts

iOS UIScrollView with multiple pages visible

I'm having a little issue with scrollview pagination.
My intention is to have 3 pages visible on screen and when I scroll right only on page is scrolled, following the example below, scrolling right will display page 2, 3 and 4 on screen:
However I don't know how to display mutiple pages at the same time, at the moment I have it like this:
Obviously like this is not what I want.
To achieve the desired functionality I tried making the scrollview's frame the size of the page I want (1/3 of the screen width) and setting the clipToBound to NO so the other pages are visible. This indeed shows 3 pages in the screen; however since the scrollview frame is only 1/3 of the screen I can only swipe in that area, I would like the swipe area to be the whole screen width.
You're on the right path. Now you can try manipulating UIPanGestureRecognizer of your scrollView, say, re-attaching it to scrollView's superview.
Alternatively, take a look at iCarousel, it can be perfectly customized to suit your needs.
My solution in the end was the following:
I took my initial approach.
I disabled the scrollview's scrollEnabled property.
Added swipe gesture recognisers to the scrollview.
When the gesture is made I modify the scrollview's contentOffset to move 320/3 pixels to the right or left.

personalize UIScrollView

I have implemented a UIScrollView for my game board, this board is a UIView consists of several images, the image of the board and some images of detail. And the zoom and scroll work well.
But what I do is that when I move the board will not let me get out of the board and see the bottom. Right now when I move the board around the edges sometimes see white, as my picture moves more than up to the stop.
What I want is to put a cap on scrollview not see what's behind only see the board.
I would also like to know if any of you know how I can do to drag images as a given within the board, as this all in a UIScrollView and I recognize there touch.
Thanks in advance
What I want is to put a cap on scrollview not see what's behind only see the board.
Yes, you can limit the visible area to just your content. Set the scroll view's bounces property to NO and make sure that the contentInset is 0. (0 is the default for contentInset, so it should be fine if you haven't changed it.)
how I can do to drag images as a given within the board
Scroll views have the option to delay touch events in the content view until the scroll view decides whether or not the user is trying to scroll. Read about the delaysContentTouches property. You'll probably want to leave that property set to YES. Beyond that, you really just need to learn to monitor touches -- you can drag a view by adjusting its position (by setting it's center, for example) as a touch moves around the screen. There's some sample code that may help you in Handling Swipe and Drag Gestures.

How to create UIScrollView with different page sizes (page snapping, bounce)

I'm trying to create custom vertical UIScrollView which could handle multiple pages with different page heights.
Assumptions:
page height is equal or greater than screen height
if page is taller than screen height, it scrolls as usual UIScrollView – with bouncing on top and bottom
if user ends up scrolling and "page break" is in the middle of screen
if there is no velocity - page snaps to closest
if there is velocity - page changes to one in direction of swipe
I've tried many approaches to achieve this, but I've stumbled upon many UIScrollView quirks, which make it hard.
Problems:
UIPanGestureRecognizer has unreliable method for getting velocity (velocityInView:)
scrollViewWillEndDragging:withVelocity:targetContentOffset: method gives me headache, because it arbitrarily can destroy my attempts to animate setting content offset
I don't know how to achieve bounce in one of the middle pages, I'm afraid i would have to rewrite whole scrolling handling
when I try to override setting content offset when UIScrollView is decelerating, what I get is
my content offset is set
deceleration continues beyond content offset I set
Bonus
I have also tried putting UIScrollView inside UIScrollView as a page, but this approach was also pain in the neck. For example when I was at the bottom of inside scroll, then i scrolled down a bit, put my finger away and quickly grabbed again and scrolled upwards, the outer scroll received touch, which messed up inside scroll presentation.
Does somebody have any idea how to do this? Any tips will be helpful as I'm completely stuck...
Try this. Might help. Based on the Circa news app.
https://www.cocoacontrols.com/controls/rscircapagecontrol

Enlarging swipe angle in which a horizontal swipe is recognized in nested UITableView

I am trying to build a UI in iOS in which there is an outer paging UIScrollView inside of which are multiple UITableViews, each representing a page. The problem I am encountering is that the angle at which I must swipe to trigger the outer paging UIScrollView to scroll horizontally is too narrow. Here is a screenshot showing what I mean.
Essentially a user has to swipe at a near-perfect horizontal angle in order for the UITableView to ignore the gesture and pass it along to the outer UIScrollView, thereby triggering a horizontal page movement. This behavior quickly becomes annoying.
I have done extensive research on stack, to no avail. Some people suggest setting directionalLockEnabled, but that has no effect here (the inner UITableView has its direction locked, it's just too greedy with the angles it interprets as going in its direction). Others suggest modifications to the contentSize, but the contentSize of the views is, in point of fact, correct. Still others suggest using Gesture Recognizers, but those seem to have a delay before they kick in.
Can anyone suggest how to accomplish the behavior in the image above?

Resources