Swift UITableView Swipe During Scroll Animation - ios

I've looked around and tried everything I can think of sort of subclassing TableView, but I think I'm missing something. I have a TableView with entries that I can swipe left and right on, and everything works fine. However, if:
1) I start (vertically) scrolling a little bit, and then swipe, the TableView's superclass, ScrollView, seems to block the swipe from my TableViewCell.
2) I stop scrolling, but the animation hasn't fully stopped, the swipe is still blocked from the TableViewCell.
How can I allow my swipe to get passed through to my TableViewCell, regardless of the vertical scroll?

Swift 2.2, Xcode 7.3
I fixed this basically by doing what was suggested in this thread: Tell ScrollView to Scroll after other pan gesture
Below is code that should enable someone else that comes across this thread to scroll in a table view, and be able to swipe, without having to deal with the table view's pan gesture recognizer blocking the swipe due to the mere hint of vertical motion.
Hope it helps someone.
So (inside a UITableViewController -- non--essential code emitted):
var lsgr : UISwipeGestureRecognizer!
override func viewDidLoad(){
super.viewDidLoad()
self.lsgr = UISwipeGestureRecognizer(target: self, action: "didSwipeLeft:")
self.lsgr.direction = .Left
self.lsgr.cancelsTouchesInView = false
self.lsgr.delegate = self
}
func didSwipeLeft(leftSwipe: UISwipeGestureRecognizer){
var ip = self.tableView.indexPathForRowAtPoint(leftSwipe.locationOfTouch(0,inView: self.tableView))
print("swipe left - "+String(ip!.row))
}

Related

Refresh Controller with navigationBar that has prefersLargeTitles set to true

I am using a navigation controller and within that navigation controller, I have a VC with a tableview that is hugging the top, bottom, left and right sides of the superView. When I have self.navigationController?.navigationBar.prefersLargeTitles set to false, everything works fine. But when I set it to true, I have to drag really really far down to get my refresh controller to trigger the refresh. I have to drag so far down that I have to use two fingers on the screen to drag down. Am I doing something wrong here or is this an issue with Apple? Here is how I am adding the refresh controller. I didn't include the handleRefresh function because the code doesn't even reach that point.
var refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action:
#selector(FeedViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
refreshControl.tintColor = UIColor.gray
self.tableView.refreshControl = refreshControl
}
I am using swift 4 and iOS 11 with Xcode 9. The cells in my tableview are large - one cell is around 400-500 points so almost the size of the screen.
What happens is, the animator starts to animate but when it gets a certain (the point where it should refresh), it just stops. I have included a screenshot of the point at which it stops. Then when I keep on scrolling down further and further, it does the refresh. But it should have done it much earlier. I have to scroll down to the point where the nav bar stretches to almost the entire length of the screen.
self.tableView.addSubview(refresh)

Sliding tableViews (much like calendar)

What do you recommend would be the best way to create the sliding tableView effect seen in calendar, where you can "swipe" between tableViews?
Currently, the way I have it working is detecting swipe gestures, and then reloading 1 tableView based on the swipe direction. While this works, it doesn't look all that great. I really want the effect where as they drag right/left the next tableView is dragged in.
var rightRecognizer: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipeFrom:")
rightRecognizer.direction = .Right
var leftRecognizer: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipeFrom:")
leftRecognizer.direction = .Left
self.view.addGestureRecognizer(rightRecognizer)
self.view.addGestureRecognizer(leftRecognizer)
func handleSwipeFrom(gesture: UIGestureRecognizer) {
println(previousDays.count)
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Right:
//call function to get new data
self.tableView.reloadData()
case UISwipeGestureRecognizerDirection.Left:
//call function to get new data
self.tableView.reloadData()
}
default:
break
}
}
}
To achieve this , instead of using TableView, you can use UICollectionView.
Please refer following reference links to implement UICollectionView;
1) Link 1
2) Link 2
3) Link 3
Hope this will help.
What I have done in the past is create a UIScrollView that will scroll horizontally and add each TableView inside of that side by side. You can then set the pagingEnabled property to yes so that when you scroll horizontally the scrollView will snap to show only one tableView at a time.
It is really quite fluid and simple to implement.
You should use collection view and in each collection view cell contains tableview. you can also use scrollview but in scrollview you have to manage content-size and other thing and scrollview don't deallocate memory after scrolling while in collection view only load current cell memory.

Handling UIPanGestureRecognizer gestures for multiple Views (one covers the other)

Sorry for such a long question, but felt I should convey what I have tried.
I've got a view viewA within a navigation controller. I am then adding a subview viewB (that contains a UITableView) to viewA and offsetting its origin height so that it covers only half the screen (with the other half overflowing off out the bottom of the screen). I want to be able to then drag this viewB upwards but it get stopped when it hits the bottom of the navigation bar and similarly get stopped when dragged back down when it hits the origin offset point. This I have achieved successfully.
However, I want the UITableView interaction to only be enabled when viewB is in its upper position and thus not respond to gestures in any other position. Essentially, dragging viewB up so that it completely covers viewA should enable interaction with the UITableView.
The tricky part here is that I want it to do the following:
If viewB is in its upper position so that it is covering the screen, the UITableView content offset is 0 (i.e. we are at the top of the table) and the user makes a pan gesture downwards, the gesture should not interact with the UITableView but should move viewB downwards.
Any other pan gesture in the above condition should be an interaction with the UITableView.
If viewB is in its upper position so that it is covering the screen, the UITableView content offset is NOT at 0 (i.e. we are NOT at the top of the table) and the user makes a pan gesture downwards, the gesture should interact with the UITableView.
I've been very close to achieving this but I can't get it quite right.
Attempts So Far
I'm using a UIPanGestureRecognizer to handle the dragging of the view. I have tried adding this to:
viewB with the UITableView user interaction initially disabled. This allows me to drag viewB up and down without interfering with the UITableView. Once viewB is in its upper position I enable UITableView user interaction which then correctly allows me to interact with the UITableView without moving viewB.
However, by enabling UITableView user interaction, this means touches never reach the UIPanGestureRecognizer, meaning I can never detect for the scenario described in point (1.) above and thus can't re-disable UITableView user interaction to make viewB movable again.
Maybe it is possible to do it this way by overriding the gesture recognition methods used by the UITableView? If this is possible can anyone point me in the right direction?
a new view added in front of the UITableView. I thought maybe I could forward the touch gestures to the UITableView behind it when necessary but I still haven't found a way to do this.
All I have been able to do is disable the gesture recognizer which allows me to interact with the UITableView, but then I have the same issue as above. I can't detect when to re-enable it.
the UITableView within viewB. This seemed to be the most promising way so far. By setting the return values of the following methods I can enable and disable recognition of either viewB and the UITableView.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if pulloverVC.view.frame.origin.y == bottomNavbarY &&
pulloverVC.tableView?.contentOffset.y == 0 { // need to add gesture direction check to this condition
viewBisAtTop = true
return false // disable pullover control
}
return true // enable pullover control
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer as! UIPanGestureRecognizer).velocityInView(view).y < 0 && viewBisAtTop { // gesture direction check not wanted here
return true // enable tableview control
}
viewBisAtTop = false
return false // disable tableview control
}
The top method is called first when a gesture is made (I have checked with print statements) followed by the bottom method. By making different combinations of true/false for the 2 methods I can alternate interaction between viewB and the UITableView.
To detect whether the user is swiping downwards I am calling velocityInView() on the recognizer (as shown in the bottom method). I was intending on making this check in the top methods if statement and I think this would work, however, although velocityInView() works fine in the bottom method, it does not in the top one (velocity is always 0).
I have scoured SO for some solution and find many similar queries about gesture handling for views that cover each other, but these all seem to be regarding one gesture type, e.g. pinch, on one view, and another type, e.g. pan, on the other. In my case the gesture type is the same for both.
Maybe someone has a clever idea? Or maybe this is actually very simple to do and I have made this incredibly complicated? xD
Managed to get this working.
Of the methods described in my question above I removed the top one keeping just this (it has a few changes):
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if ((gestureRecognizer as! UIPanGestureRecognizer).velocityInView(view).y < 0
|| pulloverVC.tableView.contentOffset.y > 0)
&& pulloverVC.view.frame.origin.y == bottomNavbarY {
return true // enable tableview control
}
return false
}
The if statement checks that the covering UITableView is in its upper position AND that either the user is is not dragging downwards or the table content is offset (we are not at the top of the table). If this is true, then we return true to enable the tableview.
After this method is called, the standard method implemented to handle my pan gesture is called. In here I have an if statement that sort of checks the opposite to above, and if that's true, it prevents control over the covering viewB from moving:
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let gestureIsDraggingFromTopToBottom = (recognizer.velocityInView(view).y > 0)
if pulloverVC.view.frame.origin.y != bottomNavbarY || (pulloverVC.view.frame.origin.y == bottomNavbarY && gestureIsDraggingFromTopToBottom && pulloverVC.tableView.contentOffset.y == 0) {
...
This now keeps the UITableView interaction off unless its parent view viewB is in the correct position, and when it is, disables the movement of viewB so that only interaction with the UITableView works.
Then when, we are at the top of the table, and drag downwards, interaction with the UITableView is re-disabled and interaction with its parent view viewB is re-enabled.
A wordy post and answer, but if someone can make sense of what I'm saying, hopefully it will help you.

Recognize swipe gesture in view not in subview

I have added a subview to a View Controller's view. This subview is the view of QLPreviewController.
What I am trying to achieve is to recognize swipe gestures on the subview in the parent view, i.e. the View Controller's view. In the end, I want to be able to swipe left /right on the view to load the next document for preview.
I'm aware of hit testing and understand that by just attaching a gesture recognizer to the parent view, those will not be recognized, since the subview will be the "hit-test" view.
Now what is the best (or easiest) way to recognize those gestures?
Note: I didn't manage to attach the gesture recognizers to the subview, this doesn't seem to work.
* UPDATE *
To make this more clear - this is the code from my ViewController. vContent is just a view in my ViewController, where I add the view of the QLPreviewController:
let pvVc = QLPreviewController()
pvVc.dataSource = self
vContent.addSubview(pvVc.view)
I tried adding the swipe recognizers both to the vContent and the pvVc.view. In both cases no event was fired.
let sgrLeft: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
sgrLeft.direction = UISwipeGestureRecognizerDirection.Left
sgrLeft.delegate = self
On some other view the code works fine.
Any hint is appreciated!
Thx
Eau
Well, the responder chain, the unknown animal … ;-)
You can subclass the superview and override -hitTest:forEvent:.
You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
Gesture Recognizers Get the First Opportunity to Recognize a Touch, so even the subview is hitTest view. the gestureRecognizer attached on superView can recognizer touch event.

UIGestureRecognizer on UILabel inside UIView inside UIScrollview

I have a programatically created UILabel that uses autolayout inside a UIView, inside a UIScrollView. Initially it is off-screen and then slides onto screen (by animating the change of the autolayout constraint constants). I am trying to add a gesture recognizer (single tap) to the UILabel but the gesture never gets recognized. If I add one to the UIView, the gesture recognizer works. Does anyone know what the solution to this would be? Is this a problem caused by autolayout?
Thanks.
EDIT
This is definitely to do with the scrollview swallowing touches. I've just created the same label outside of the scrollview and the gesture recogniser works fine!
EDIT 2
I can create the label within the scroll view using Interface Builder, but programmatically it doesn't work...
You have to check User Interaction Enabled in the UILabel
If you are adding a UITapGestureRecognizer programmatically:
In viewDidLoad i added the following code:
let gesture = UITapGestureRecognizer(target: self, action: Selector("myaction"))
self.label.addGestureRecognizer(gesture)
with the selector:
func myaction() {
println("Label tapped")
}
where self.label is the reference outlet of the label being tapped.
Turns out the label was embedded within 2 views, within the view in the scrollview. Taking it out of this seemed to solve them problem...

Resources