I want to have a scrollview that covers the entire screen. However I want to know if it is possible to only allow scrolling to be detected from part of the scrollview. For example you have a full screen scroll view the top half of the screen should detect scrolling but the bottom half should not. I know if you change the alpha to 0 the scrollview doesn't scroll anymore, would a possible solution be to change the alpha of part of the scrollview? is that even possible? any thoughts or ideas?
You can subclass from UIScrollView and override method touchesShouldBegin.
You should check at what point touches and allow it or not.
class ScrollView: UIScrollView {
override func touchesShouldBegin(_ touches: Set<UITouch>, with event: UIEvent?, in view: UIView) -> Bool {
if (touches.first?.location(in: self).y)! > self.bounds.height / 2 {
// In bottom part
return true
}
// In top part
return false
}
}
Related
I'm building a collectionview. Below of it I placed some buttons as shown in the picture.
What I want is to make the UICollectionView background pass taps below, so the desired buttons can receive taps.
I don't need to add Tap gesture recognizers to the background view (the problem I'm describing is just an example here), I need the buttons' actuons to be triggered directly when they're tapped.
I thought I could do this by making the background clear or disabling user interaction for the background view. While disabling it for the entire collection view works, this other way does not.
How can I make the background view of my collectionView be "invisible" so that taps go straight to the below buttons instead of going to the collectionview background?
The following is an example of my layout.
Assuming your collectionView and your buttons share the same superview, this should do the trick.
What you want to do is bypass the backgroundView and forward hits to the subviews underneath the collectionView.
Notice that we are picking the last subview with the matching criteria. That is because the last subview in the array is the closest to the user's finger.
class SiblingAwareCollectionView: UICollectionView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hit = super.hitTest(point, with: event)
guard hit === backgroundView else {
return hit
}
let sibling = superview?
.subviews
.filter { $0 !== self }
.filter { $0.canHit }
.last { $0.point(inside: convert(point, to: $0), with: event) }
return sibling ?? hit
}
}
If you look at the documentation for hitTest(_:with:) it says:
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01.
For convenience, here is an extension to ensure we are playing by the rules:
extension UIView {
var canHit: Bool {
!isHidden && isUserInteractionEnabled && alpha >= 0.01
}
}
Below view is a tableViewController which is implemented and set the delegate and datasource methods and it works perfectly fine but I have no idea how to implement the purple view behavior on scrolling up and down.
How can I implement it?
The below code is the way I detect that view scrolled up or down. but still, no clue to implement this behavior.
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
if translation.y > 0 {
presenter?.viewScrolledDown()
} else {
presenter?.viewScrolledUp()
}
}
this question covers how to animate a backgroundColor. If the purple view is a UIView, this won't be a problem. It starts behind the tableView and then moves to the front of the tableView. You can handle this by using these functions:
view.bringSubviewToFront(purpleView)
view.sendSubviewToBack(tableView)
You will also need to animate the autoLayout constraints to move the bottom of the purple view up to the top of the tableView.
I have a layout with a UIView at the top of the page and, right below it, I have a UITableView.
What I am wanting to do is to transfer the gesture interactions on the UIView to the UITableView, so when the user makes a drag up/down on the UIView, the UITableView scrolls vertically.
I tried the following code
tableView.gestureRecognizers?.forEach { uiView.addGestureRecognizer($0) }
but it removed the gestureRecognizers from the UITableView somehow :/
Obs.: the UIView cannot be a Header of the UIScrollView
That's Tricky
What is problem ?
Your top view is not allowed to pass through view behind it...
What would be possible solutions
pass all touches to view behind it (Seems to not possible or very tough practically )
Tell window to ignore touches on top view (Easy one)
Second option is better and easy.
So What you need to do is create subclass of UIView and override
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
and return nil if you found same view on hitTest action
Here Tested and working example
class PassThroughME : UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event) == self ? nil : self
}
}
That's it now use PassThroughME either by adding class to your view in storyboard or programmatically whatever way you have added your view
Check image i have black color view with 0.7 alpha on top still i am able to scroll
Hope it is helpful
Issue: The viewWithGesture contains the viewUserSees, and is draggable within the blue containerView. However, the viewWithGesture is a subView of the containerView, so when the viewWithGesture is at an extreme (illustrated here - half in and half out of the containerView), only half of the viewWithGesture responds to touches, making it very hard to drag.
Note: I realize I should redo all the math that keeps it in the container and move it outside of the containerView, but I am very curious to learn how to do this the "worse" way.
I have researched this a bunch and tried to implement hittest() and pointInside(), but so far I have managed to just make the app crash spectacularly.
Is there a good, relatively clean way to let the user grab from outside the containerView? (swift3 if possible)
EDIT: The green box is transparent and half of it is in the containerView and half is not.
In order for a view to receive a touch, the view and all its ancestors must return true from pointInside:withEvent:.
Normally, pointInside:withEvent: returns false if the point is outside the view's bounds. Since a touch in the green area is outside the container view's bounds, the container view returns false, so the touch won't hit the gesture view.
To fix this, you need to create a subclass for the container view and override its pointInside:withEvent:. In your override, return true if the point is in the container view's bounds or in the gesture view's bounds. Perhaps you can be lazy (especially if your container view doesn't have many subviews) and just return true if the point is in any subview's bounds.
class ContainerView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if super.point(inside: point, with: event) { return true }
for subview in subviews {
let subviewPoint = subview.convert(point, from: self)
if subview.point(inside: subviewPoint, with: event) { return true }
}
return false
}
}
Here's a scroll view (it is the GRAY area). Content area is yellow. Notice it insets, so that you can scroll the sides of the content over to the middle of the screen.
PROBLEM: you can put your finger on the GRAY area and scroll it.
I want it to work so that you can ONLY scroll by putting your finger on the yellow content. If you put your finger on the gray "background" and try to scroll it, nothing should happen.
How to do this?
Yellow content is scrolled fully to the right, it rests here:
Yellow content is scrolled fully to the left, it rests here:
Make a subclass of UIScrollView. Give it a reference to the yellow view. Override pointInside:withEvent: to return false unless the point is in the frame of the yellow view.
class MyScrollView: UIScrollView {
#IBOutlet var yellowView: UIView?
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return yellowView?.frame.contains(point) ?? false
}
}
This should work.
in scrollViewWillBeginDragging you remember the coordinates where dragging begins, in scrollViewDidScroll you determine if dragging began inside yellow area and just do nothing (allow scroll to happen normally), in other case you return ScrollView contentOffset property to the coordinates that it had at the moment of scrollViewWillBeginDragging