I have a main menu with a list of buttons. On the bottom I have a bar that the user can flick up to the top via a scrollview. Whats underneath that bar however is a uiwebview. So when the user tries to scroll in the uiwebview, the bar and webview just snaps back down. Also, the uibuttons won't work when the bar is down because the scrollview is sitting over them. How do i make the scroll only work when the user touches the bar and make the buttons on my menu work when the bar is down?
Thanks in Advance!
Where you want the scrollView to "work" (maybe when user flicks the bar up) have the following code:
scrollView.userInteractionEnabled = YES;
and wherever you don't want the scrollView to register any user actions (like when the bar is down) just do the opposite and set it to NO.
So if I understand correctly, you want the UIScrollView to respond to touches in only a certain part of the frame (the bar)?
I would suggest taking a look at one of my previous answers to a similar problem, here.
In particular, see:
This answer to the SO question Touch and pull down a view. It uses the hitTest:withEvent: method to determine whether the scrollview responds to a touch or leaves it for the view under it to respond to.
This answer to the question Drag Down UIView in iOS 5 works in a similar way, using pointInside:withEvent: to make the UIScrollView only respond to touches in a given area.
This answer to the question Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related? gives a good overview of how hitTest:withEvent: and pointInside:withEvent: work together.
Related
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
I have what should be a very simple thing to do. I'm working on someone else's code, and I want to enlarge a UIButton because it's too small for users. I made it bigger in the storyboard, but when I run the app, the associated IBAction only gets hit when touching where the original rectangle was before I changed it. The button is still visibly larger, but only a portion of it receives touch events. Does anyone know what else might be at play here?
Note: there are no views on top of the new area that the button occupies, so I don't think the touches get picked up by a view on top.
Something to check is whether the UIButton has an ancestor view (i.e. a view in its superview chain) that is the smaller size. Hit-tests only pass down the view hierarchy if the touch is contained within the view so a smaller superview will stop the touches outside its bounds, even if the touch is inside the button.
Is the IBAction hooked up to "touch up inside" in Interface Builder/Storyboard? I've made mistakes where I hook it up with a different kind of event, which exhibits behaviours like you're experiencing.
Found the issue. There was a view being programmatically added on top of the button. It's origin.x was being hardcoded to where the buttons width use to end.
I have a UIPageViewController which manages UINavigationControllers, which are hooked up to a UITableView. I want the user to be able to swipe between the different "table views", which currently works. When the user swipes on the first or last page, the controller moves off the screen and there is black behind it. I don't want the user to be able to swipe off the screen.
I tried using gesture recognizers to prevent pan and swipe gestures in a certain direction when the user was at the first or last page but when I returned NO in gestureRecognizer:shouldReceiveTouch: the view was still able to scroll.
In the end I am trying to emulate how "snapchat" works, I don't mind using a different method to achieve what they have I just am unsure how I would do it.
You can try setting background color on the superview to white or the color of your tableview.
I finally figured out an easy semi-fix. I decided to take a screenshot and use the clone tool to get rid of all the labels and I set it as the background image of the UIPageView. Now at least it doesn't look black, it just looks like the tableview continues off the screen.
Think of a UIScrollView with embedded (subview) UIViews, arranged in a column. In iOS6 and previously, the scroll view could be configured so that when you flick it with your finger, the embedded views do not receive touch events even if the initial touch is on one of the subviews; but if you touch a subview without flicking, the scroll view decides this is not a scroll action and forwards the touch events to the embedded views. This was very convenient behavior if you wanted to be able to drag/drop the embedded UIViews within the UIScrollView.
In iOS 7 the documentation indicates that setting the UIScrollView property delaysContentTouches will cause touch-down events to be delayed until the UIScrollView decides whether it's being scrolled. But in fact, this simply does not appear to work. The subview immediately receives touch events and responds to them if the scrolling touch-down event is on one of the subviews. Thus if the subview is programmed for drag/drop it starts dragging while the scroll view also scrolls.
It appears that the model for this behavior has been changed, since iOS 5/6 both did suppress touch events while deciding whether this is a scroll action. Some new methods are now available to cancel the touch-down events after the UIScrollView decides it is scrolling. But obviously this is not useful if the drag/drop code has also started moving the subview.
My question: Is there any way to prevent iOS 7 UIScrollViews from invoking low-level touch-down events on its subviews, when you initiate a scrolling action by stroking a subview?
When I say "low-level", I mean actual touch events as opposed to using gesture recognizers. I am convinced that simply setting the delaysContentTouches property to YES does not work.
This problem has totally busted some quite complex code that worked smoothly and beautifully in iOS 5 and 6; and, so far I have discovered no way to tell the UIScrollView to suppress events to its subviews until it determines whether or not it is being scrolled. The events go through, then a cancellation touch event is triggered later, after the scroll view determines it is scrolling. It looks like the underlying model has been redefined for the worse, or this is a bug. Any insights will be greatly appreciated.
Are you sure it was working on iOS5/6? Based on my experience and posts like UIScrollview delaysContentTouches issue it doesn't work.
Maybe a sample code which shows it working on iOS5/6 but not on iOS7 will help to answer your question.
This works for me:
[scrollView setCanCancelContentTouches:YES];
[scrollView setDelaysContentTouches:YES];
EDIT: The crux of this problem is that scroll indicators do not show during programmatic scrolling, but I would like them to. My original question (provided below) assumed this had something to do with userInteractionEnabled, but it does not. The mention of a master and slave UIScrollView is also possibly distracting from my core problem (the need to show scroll indicators during a programmatic scroll). Apologies to those of you who answered or commented based on my misleading assumptions/info.
Possible Solution: The only way I found to do this was to use the fact that scroll indicators are instances of UIImageView and use a category on it to hack the alpha. This article shows the approach. It was then a case of using tags and scroll view delegate methods to turn the alpha permanently on prior to a programmatic scroll, and permanently off when the scroll is finished. This feels hacky though, so any further suggestions would be welcome!
Everything below this line is the original unedited question to provide context to users' answers and comments
Setting userInteractionEnabled in a UIScrollView object to NO appears to disable the scroll indicators upon programmatic scrolling. This happens even if you have self.showsVerticalScrollIndicator = self.showsHorizontalScrollIndicator = YES;
Is there any way to programmatically scroll the scroll view but still show the indicators?
(To provide some context: I have a slave scrollview that mimics a master scrollview by hooking up the scrollview delegate callbacks and passing the content offset to the slave scrollview. However, I don't want the user to be able to directly manipulate the slave scrollview, but I do want scroll indicators).
Instead of setting userInteractionEnabled to false try setting the UIScrollView's scrollEnabled property to false. The doc. says "When scrolling is disabled, the scroll view does not accept touch events" that should mean that you should still be able to programmatically scroll the UIScrollView. Hope this helps - Did not test it out let me know.
You could try putting a transparent UIView (alpha == 0.0) over your scroll view (but as a sibling in the view hierarchy, not as a subview). Set touchesEnabled to YES on the transparent view, and it will intercept touches heading for the scroll view.