Trouble handling interactive state for my UIView - ios

I'll start with my view hierarchy:
ParentView -----> TopBarView (UIView) and CollectionView (UICollectionView)
Scenario:
1) My TopBarView is pinned to the top of ParentView with a height constraint. It is set to handle tap and long-press gestures. Also, my TopBarView's width is equal to that of CollectionView
2) My CollectionView is pinned to the top of the ParentView too! And it has a top padding equal to that of the TopBarView's height. So visually, CollectionView's content starts right below the TopBarView. Also, whenever the CollectionView is scrolled, my TopBarView's constraint is adjusted based on my CollectionView's offset, to visually make it look like it's scrolling along and the inverse is what I'm working on...
My requirement:
I'm trying to make the TopBarView scrollable, so that the CollectionView scrolls along with it too
What I've tried:
To establish scroll behaviour, I'm simply setting the isUserInteractionEnabled property of TopBarView to false, so that the touch events get passed to the underlying CollectionView, and it works as intended!
The Problem:
Since I already support tap gestures and long-press gestures for my TopBarView, I cannot permanently set it to be irresponsive. I have to toggle between the gestures that the user performs i.e: To accept interactions where the user taps or long-presses the view, but deny interactions when user tries to scroll the view(so that the touch events get passed to CollectionView)
All of the above is mostly just for the context. Here's my problem put simply:
My view should respond to taps and long-presses. But when scrolled, it should quit responding and pass the scroll to underlying collection view.
There's got to be something probably trivial that I'm missing out on, and I'm here. TYIA!

Related

Horizontal scroll in collection view

I wish to do something when my collection view cell is scrolled horizontally. But I do not know which function gets called when it is getting scrolled.
I have a collection view cell inside of a table view cell, and the collection view cell scrolls horizontally
If you use storyboard, in Attributed Inspector you will find an option that Scroll Direction, make it Horizontal, default this is set to Vertical
programitically
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
There are several methods that will notify you when a collection view gets scrolled, all of them are contained in UIScrollViewDelegate, and thus in UICollectionViewDelegate protocol, which inherits from the former.
scrollViewDidScroll(_:) is indeed called each time the content offset changes. That means not only the active scrolling issued by the user, but also scrolling by inertia, programmatic scrolling and bouncing. You can use this method to react to the scrolling distance, for example, by querying the contentOffset property of the scroll view.
scrollViewWillBeginDragging(_:) is, in contrast, getting called only at the beginning of scrolling issued by the user (that's why in the documentation you can see that this method may be called only after some delay, since the scroll view's gesture recognizer needs time to decide if it's a tap or a pan gesture). It will not be called again until the user lifts their finger and starts scrolling again.
scrollViewWillBeginDecelerating(_:) is called when the user-issued scrolling discussed above ends, but the scroll view will continue scrolling further to achieve this inertia feeling. Again, it will not get called again until the user lifts their finger one more time.
That's basically it. If this still doesn't narrow down the event that you want to track (for example, you want to become notified when the user starts scrolling at the initial position only), you will need to set some flags or track additional properties.
To track horizontal scrolling for instance, you will need to either compare the scroll view's previous content offset to the current or check the scrolling velocity via scrollView.panGestureRecognizer.velocity(in: collectionView).
First, set your collectionview's delegate to your viewcontroller (or view)
Implement UIScrollViewDelegate methods inside your viewcontroller and you can track your collectionview's scrolling.

How to create up/down swipeable UITableView?

I have a UITableView in the bottom of UIViewController and tableview height is 100 point now.
The tableview has 20 cells and tableview's header view is 100 point. And I've added a up UISwipeGestureRecognizer and a down UISwipeGestureRecognizer in table view header.
Now I want to change the tableview height constraint constant to 400 in up gesture action and change the tableview height constraint constant to 100 in down gesture action.
Now the problem is gesture recognizer isnt working in tableview
header when tableview scroll is enabled.
If tableview scroll is disabled then the gesture recognizer is
working. But unable to view all cells once the tableview height is
changed.
Here's a different approach. Don't use swipe gesture recognizers at all.
Instead, make the table always the full 400 points tall. Set its contentInset.top to 300. This will allow the table view to scroll so that only its top 100 points of content are visible at the bottom of the screen. Specifically, the table view will allow its contentOffset.y (which is its vertical scrolling position) to go down to -300 (instead of only down to 0). The table's content always starts at y = 0, so when the table's contentOffset.y is -300, only the top 100 points of its content are visible. As the contentOffset.y increases, more of its content becomes visible.
Then, override the table view's point(inside:withEvent:) method to return true only for points with y >= 0. This means the table will ignore (pass through) touches above its content when its content is scrolled so only the top 100 points are visible.
This is the final effect for a small table:
or for a big table:
You can find a detailed explanation (in Objective-C) and a link to the full test project (also in Objective-C) in this answer.
As it may help others, here is the simplest possible solution to this type of problem.
Extremely simple solution -
no tricks, nothing fancy -
don't have a "table view header".
Just make a new UIViewController
class TableWithRedTop: UIViewController
that has ...
[ red area .. swipe detection ]
[ main area - the table view]
Then simply put 'TableWithRedTop' inside a container view.
(Container view tutorial if you need one.)
In your MainView just have a call
func toggleTableHeight
When 'TableWithRedTop' gets a swipe, have one line of code to call toggleTableHeight in MainView
Note that in toggleTableHeight you can easily animate the height change, tilt it on an angle, duplicate it or do anything you want, as you're using a container view in MainView.
I'm going to make another suggestion that may solve the issue for you.
It could be what you want is an:
"self-expanding" table...
First implement the following with no animations, for simplicity.
You have two heights for the table, small and large.
Start the table on height small.
Remarkably, you only have to implement these two rules: just two simple lines of code:
Any time the user is scrolling upwards - in fact, change to "height large".
Any time the user is scrolling downwards, and, you are at the top position of the table (i.e. you can see cell #1) in fact change to "height small".
It's one of those things that is "so simple, it's hard to believe it works!"
It's sometimes referred to as a "pull-up table" I think.
(Note. If you're not familiar with detecting when the user is scrolling, fortunately it is trivial - code shown here for example.)
Set both swipe gesture’s cancelsTouchesInView to true and make sure that the gestures are added directly to the header and not the tableView.
That should do the trick, but so should adding a view with the gesture recognizes to the container view and setting it’s constraints to match the tableView’s top, left, and right constraints and setting its height to 100.

UIKit collection view, strange behavior (accessing/scrolling cells)

So I recently implemented a collection view in my app, and I got a bug that I can't seem to solve, searched it and saw no threads about it.
If I have my cursor/finger over the cells i can't scroll through my collection view i need select a "empty" area to scroll.
Second strange Behavior I came across is that I can't directly touch a cell. I need some sort of swipe gesture over it to trigger the code when a cell is selected.
If I go to my collection view on my storyboard and select Delays Content Touches and Cancellable Content Touches in the scrollview section, the collection view scrolls just fine but if I put my finger/cursor over a cell with these option enabled I can't access any cells anymore.
This completely confuses me.
and thank you for reading/considering this thread.
Let's see what your two properties do.
delaysContentTouches: If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false , the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true.
canCancelContentTouches: If the value of this property is true and a view in the content has begun tracking a finger touching it, and if the user drags the finger enough to initiate a scroll, the view receives a touchesCancelled(_:with:) message and the scroll view handles the touch as a scroll. If the value of this property is false, the scroll view does not scroll regardless of finger movement once the content view starts tracking.
First, you set delaysContentTouches to false. So the scrollview immediately calls the content view's touch handling methods, allowing it to handle the touch. Obviously, the scroll view won't start scrolling right away because of this, even if you drag.
Second, you also set canCancelContentTouches to false. But if the scroll view isn't allowed to "take over" touches that the content already handles (by cancelling them), it is never able to start scrolling later on either. So if your touch hits a content view, there is no possible way for the scroll view to start scrolling: it isn't allowed to scroll right away because it isn't allowed to delay the content touches, and it can't start scrolling later because it can't cancel the content touches.
I don't know what happens within your cells, not sure what code you put in there. However, you should probably allow your tableview to both delay touches (that means that your cell won't handle swipes that are cancelled immediately anyway because they were intended to be scroll gestures), and to cancel content touches (that means that when you touch down and don't release, you can still start a scroll gesture after a cell became highlighted).
i had the same problem when touching a cell, the problem was that I'm using more than one UIGesture without adding ".cancelsTouchesInView = false" for each one
so if you're using a UIGesture just add Your_Gesture.cancelsTouchesInView = false
and you should be able to access your cells

Detect touches under a UIScrollView

I built a project that has a mapView (Google Maps), tableView and a UIScrollView.
The tableView is on the bottom and when the app starts you see a really small part of it and the most of it is out of the screen (on the bottom). When the user scrolls the tableView is revealed and going up from the bottom until it covers all of the screen. It's all working but I have one small problem: I want that the user will be able to touch the map (zoom, move, etc.) but the UIScrollView is on top of the map and when the user tries to interact with the map the scrollView detects a scroll and the map doesn't get a touch event. Is there any way that the UIScrollView wouldn't detect the touch on the area of the map? The scrollView should cover all of the screen for the tableView detection (or maybe not and there is another way?) and I don't want the map to be a subclass of the scrollView because it will go up with the table when the user scrolls (I want the tableView to go on top of the map and not with it).
Thanks!
Going off of my comment. You will need to set the top space to superview constraint on the scroll view. Create an IBOutlet for the constraint and a float variable for the value of the constraint and also connect it to your top space to superview constraint that you will create in interface builder on the scrollview.
in your .h:
IBOutlet NSLayoutConstraint *topSpace;
float topSpaceValue;
Then update that constraint using this:
topSpace.constant = topSpaceValue;
You can detect scrolling in the scrollview with this method and set the topSpace constraint:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
topSpace.constant = topSpaceValue;
}
Now, the actual logic of detecting how fast the scrollview is moving and what value to set for topSpaceValue is up to you to figure out. Also you will need to detect what direction the scroll view is moving to increase or decrease the value of topSpaceValue.
This looks like the link you need for detecting the speed: iPhone UIScrollView Speed Check
So according to the speed, set the constraint. And you will probably want to check for a minimum value for the constraint, like 0, so the scrollview doesn't scroll off the top of the screen.

Prevent overlapping UIScrollView from scrolling

I have a UIScrollView where one of the elements, when touched, pops up another UIScrollView, of the same size (full screen) as the underlying view. I want this top scrollView, when shown, to be the only element responding to touches, but as it is, if the top scrollView runs out of content, the underlying scrollView picks up the touches and scrolls its content. How can I force the responder chain to stop at the top UIScrollView without putting it in a separate UIViewController?
Try setting scrollEnabled to NO on the base scroll view when the "top" scroll view is shown, and re-enable it when you want control to return.

Resources