UITableView Subview when swipe to show "extras" - ios

I am trying to implement a "extras" menu for a app I am creating. Essentially I have a UITableView with multiple types of cells, when a user swipes to the left on the cell I want to be able to dynamically show them "extras" they can do. For example on a post they can swipe over and then see options like share, like ...ect. If you have used the Alien Blue app on ios for Reddit then that is what I am looking to do...
So far I have the swipe recognizer working and it identifies the type of cell properly... I just don't know how to start the subview programming...
Do I just make every cell larger and hide the extras until swipe or do I dynamically add views to each cell as I go...
Thank for any advice or help
I can provide code if needed....
A

I think you are right, and at least, that's how I did on my cell.
The only difference is i am not making the cell width large than window width. I add a view with the extra stuff, in my case it's a delete button, under the regular cell.contentview. then when the cell detects a swipe from right to left, it will call a function to handle the gesture.
Since i want the user to see that delete button when drag the cell to the left and show that button entirely when they pan the cell far enough
here is a snippet for how i handle the pan gesture,
CGPoint _originalTouchPoint;
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
//save the original touch point when pan starts
_originalTouchPoint = [recognizer locationInView:_tableView];
break;
case UIGestureRecognizerStateChanged:
[self handlePan:recognizer];
break;
case UIGestureRecognizerStateEnded:
[self panEnded:recognizer];
break;
default:
break;
}
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
MyCell *ourCell = (MyCell *)recognizer.view;
CGPoint touchPoint = [recognizer locationInView:_tableView];
float movedDistance = (_originalTouchPoint.x - touchPoint.x);
if(movedDistance < 0)
movedDistance = 0;
float maxX = ourCell.deleteButton.frame.size.width;
float ourTranslation = MAX(-1 * ourCell.deleteButton.frame.size.width, -1 * movedDistance);
ourCell.contentView.transform = CGAffineTransformMakeTranslation(ourTranslation, 0.0f);
// i only show the button when user pan all the way though
_shouldDeleteButtonShow = movedDistance / maxX >= 1.0;
}
- (void)panEnded:(UIPanGestureRecognizer *)recognizer
{
MyCell *ourCell = (MyCell *)recognizer.view;
if (_shouldDeleteButtonShow)
{
//do whatever you want in this case
}
else
{
//move the cell back to normal
[UIView animateWithDuration:0.3
animations:^
{
ourCell.contentView.transform = CGAffineTransformIdentity;
}];
}
}
Those are my codes, maybe not working exactly like you want, but hopefully, that gave you a rough idea about how to do this

Related

Expand UIScrollView interactive area and differentiate swiping and tapping

I'm using UIScroll View to make a gallery-like ui with paging functionality. Basically like this:
Since I need paging, so I set the width of scrollview equals to the width of a single page, in my example, the width of the pink rectangular.
But I want two extra things:
Tapping the yellow or blue area will bring the corresponding rectangular to the center.
One can scroll/swipe on yellow or blue area (out of the scrollview), which means the entire width of the screen is scrollable.
I followed this thread and added - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event. BUT by doing so, I can only achieve my second goal. When I set selector or delegate handling tapping reaction of yellow and blue, it does't work. Any idea about it?
That answer you referenced is one of my old favorites. It doesn't contemplate your first requirement, but I think it can handle it very neatly with just the addition of a tap gesture recognizer.
Create it on your "ClipView":
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
[self.myClipView addGestureRecognizer:tapGR];
// myClipView is the view that contains the paging scroll view
- (void)tap: (UITapGestureRecognizer *)gr {
// there are a few challenges here:
// 1) get the tap location in the correct coordinate system
// 2) convert that to which "page" was tapped
// 3) scroll to that page
}
Challenge 1) is easy thanks to the gesture recognizer, which answer locationInView:
CGPoint location = [gr locationInView:self.scrollView];
For challenge 2) we need to work out what page within your scroll view was tapped. That can be done with pretty simple arithmetic given the page width.
// assuming you have something like this
#define kPAGE_WIDTH // some float
// page is just how many page-width's are represented by location.y
NSInteger page = floor(location.y/kPAGE_WIDTH);
Now, challenge 3) is easy now because we can change a page to it's scroll position straight-forwardly...
CGFloat y = page * kPAGE_WIDTH;
[self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];
Or, all in one chunk of code...
- (void)tap: (UITapGestureRecognizer *)gr {
CGPoint location = [gr locationInView:self.scrollView];
NSInteger page = floor(location.y/kPAGE_WIDTH);
CGFloat y = page * kPAGE_WIDTH;
[self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];
}
EDIT
You may also want to exclude the "current page" area from the gesture recognizer. That's simply done by qualifying the test in the tap method.
The only trick is to get the tap position in the same coordinate system as the scroll view's frame, that is, the clip view...
CGPoint locationInClipper = [gr locationInView:gr.view];
And the SDK provides a nice method to test...
BOOL inScrollView = [self.scrollView pointInside:locationInClipper withEvent:nil];
So...
- (void)tap: (UITapGestureRecognizer *)gr {
CGPoint locationInClipper = [gr locationInView:gr.view];
BOOL inScrollView = [self.scrollView pointInside:locationInClipper withEvent:nil];
if (!inScrollView) {
CGPoint location = [gr locationInView:self.scrollView];
NSInteger page = floor(location.y/kPAGE_WIDTH);
CGFloat y = page * kPAGE_WIDTH;
[self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];
}
}

Disable UITableView scrolling temporarily during drag

I am trying to figure out a way to temporarily (i.e. during a single drag gesture) disable scrolling on a UITableView and then re-enable it to have it pick up where it left off.
My reason is I have a gesture recognizer that is monitoring the drag, and if the user drags their finger above the top of the table, I want to resize the table upwards with their finger, to a point, and then stop resizing and continue scrolling again.
Naturally, I don't want the table to scroll while it is resizing, because that's effectively achieving the scroll itself (by moving the entire table view instead of the inner scrollable content), however I can't figure out how to do this in such a way that it allows the gesture to take effect again after a certain point (or if the user drags back down over the table).
Is there a way to temporarily disable/block a gesture without causing it to fail or cancel outright?
Perhaps I could write a subclass of UITableView that can intercept the gestures and ignore them as needed. What method should I override to do this?
Update:
I ended up approaching this in a different way, which is to simply adjust the contentOffset of the table view at each change of the gesture. I was afraid this might look "jittery" but it actually works quite smoothly. However I'll leave the question open as I'm still curious if this can be done.
At the request of #BrunoGalinari, here is the main part of my implementation of handling pan gestures on a UITableView without breaking the table view's intrinsic scrolling.
tableViewExpanded is a local property that switches between two layout states (expanded or not) and adjusts the bottomViewHeightConstraint constant appropriately. Setting it to itself just readjusts the constraint to one of the two valid values since it is also affected during the pan.
- (void)handlePan:(UIPanGestureRecognizer*)sender {
static CGFloat initialBottomViewY;
static CGFloat initialTableViewContentOffsetY;
static CGFloat initialTouchPointY;
CGPoint touchPoint = [sender locationInView:self.view];
CGFloat splitOffset = touchPoint.y - initialBottomViewY;
BOOL inEffect = ( sender == self.tableViewPan && touchPoint.y < initialBottomViewY ) || ( sender == self.mapViewPan && touchPoint.y > initialBottomViewY );
switch ( sender.state ) {
case UIGestureRecognizerStateBegan: {
initialBottomViewY = self.bottomView.y;
initialTableViewContentOffsetY = self.tableView.contentOffset.y;
initialTouchPointY = touchPoint.y;
break;
}
case UIGestureRecognizerStateEnded: {
self.dragVelocity = [sender velocityInView:self.view].y;
if ( inEffect ) {
if ( ABS( splitOffset ) > 60.f ) { // adjust
if ( sender == self.mapViewPan && touchPoint.y > initialBottomViewY )
self.tableViewExpanded = NO;
else if ( sender == self.tableViewPan && touchPoint.y < initialBottomViewY )
self.tableViewExpanded = YES;
else
self.tableViewExpanded = self.tableViewExpanded;
} else
self.tableViewExpanded = self.tableViewExpanded; // spring back
}
break;
}
case UIGestureRecognizerStateChanged: {
if ( inEffect ) {
self.tableView.contentOffset = CGPointMake( self.tableView.contentOffset.x, initialTableViewContentOffsetY + initialTouchPointY - initialBottomViewY );
self.bottomViewHeightConstraint.constant = self.view.height - touchPoint.y;
self.annotationToSelect = nil;
[self adjustMapAnimated:NO];
}
break;
}
default: {
break;
}
}
}
Here's what the window looks like, to get an idea of placement:
You can disable scrolling of a UITableView by setting:
table.scrollEnabled = NO;
When you are don't with you custom gesture, enable it:
table.scrollEnabled = YES;
It is a property of the parent class UIscrollView:
If the value of this property is YES , scrolling is enabled, and if it is NO , scrolling is disabled. The default is YES.
When scrolling is disabled, the scroll view does not accept touch events; it forwards them up the responder chain.

iOS - How to make objects draggable to only one direction at a time

I'm trying to make an UIViewImage, and an UILabel draggable, and i having some problems to figure out how to make the Views, only draggable at one direction, for example, i want to drag the views to left(x) or down(y), but never left and down at same time.
I tried to use the UISwipeGestureRecognizer, to see in which direction i should move UP, DOWN or LEFT and RIGHT, but things are not working as i tough.
To make things easier this is what a have on viewController.h:
///Images
#property (nonatomic) UIImageView *mainImage; // Draggable Image
#property (nonatomic) UISwipeGestureRecognizer *swipeDirection; // This will be used on TouchesMoved.
//Label
#property(nonatomic) UILabel *mainLabel; // Draggable Label
I'm adding the Swipe recognizer in the viewDidLoad Method, just for the Left (for now):
UISwipeGestureRecognizer *detectSwipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(setSwipe:)];
[detectSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:detectSwipe];
Here is where i set the "swipeDirection" property:
-(void)setSwipe:(UISwipeGestureRecognizer *)gesture {
if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) { // this is just for testing
self.swipeDirection = gesture;
NSLog(#"Gesture = %#", gesture);
}
}
Here is where I'm actually trying to move things (currently only to left):
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches ) {
CGPoint currentLocalation = [touch locationInView:touch.view];
if (self.swipeDirection.direction == UISwipeGestureRecognizerDirectionLeft) {
NSLog(#"Swipe Left");
if (currentLocalation.x < startLocation.x) { // Startlocation is a global CGPoint.
NSLog(#"Current Y = %f, starting Y = %f", currentLocalation.y, startLocation.y);
CGFloat distance = startLocation.x - currentLocalation.x;
CGPoint newPosition = CGPointMake(160 - distance, 170);
CGPoint labelPosition = CGPointMake(160 - distance, 390);
self.mainImage.center = newPosition;
self.mainLabel.center = labelPosition;
}
} else {
NSLog(#"Other direction");
}
}
What should i do? I'm looking at this from the wrong direction? Any help will be appreciated.
It looks like you're trying to use a swipe gesture for something it's not fit for (i.e. tracking finger movement and moving views along with the finger). This is exactly what UIPanGestureRecognizer is for. Here's an example of how this would work with a pan gesture:
First, attach the gesture recognizer:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(onPan:)];
[self.view addGestureRecognizer:panGesture];
Then, handle finger movement:
-(void)onPan:(UIPanGestureRecognizer*)recognizer
{
// Find out how far the finger has moved (+x is right, -x is left, +y is down, -y is up)
CGPoint movement = [recognizer translationInView:recognizer.view];
if(movement.x < 0){
// Finger moved left by movement.x pixels
}
if(movement.y > 0){
// Finger moved down by movement.y pixels
}
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}
In this example, I'm accepting both left and downwards movement at the same time. I'll leave it up to you to figure out how to decide which direction gets precedence if the user drags down and left simultaneously. One suggestion would be to compare the absolute value of the x and y directions, and act on whichever is larger
I would use a Pan Gesture recognizer, and lock the direction. I setup a simple typedef for my axis. Default means it hasn't been set yet. When the axis is not set it looks for which direct the user starts off. In this case when the users moves 20 points in one direction it will lock. You want to set a minimum threshold for this to ensure expected results.
typedef enum{
elAxisUpDown,elAxisLeftRight, elAxisDefault
}elAxis;
-(IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
CGPoint testPoint = [sender locationInView:self.view];
switch (sender.state) {
case UIGestureRecognizerStateBegan:
// #property elAxis
self.axis = elAxisDefault;
// #property CGPoints
self.startingPoint = testPoint;
self.startingCenter = sender.view.center;
break;
case UIGestureRecognizerStateChanged:
switch (self.axis)
{
case elAxisDefault:
{
if (fabsf(testPoint.x-self.startingPoint.x) > fabsf(testPoint.y-self.startingPoint.y) && fabs(testPoint.x-self.startingPoint.x)>20) {
self.axis = elAxisLeftRight;
}
else if (fabsf(testPoint.x-self.startingPoint.x) < fabsf(testPoint.y-self.startingPoint.y) && fabs(testPoint.y-self.startingPoint.y)>20) {
self.axis = elAxisUpDown;
}
break;
}
case elAxisLeftRight:
self.button7.center = CGPointMake(self.startingCenter.x + (testPoint.x - self.startingPoint.x), self.startingCenter.y);
break;
case elAxisUpDown:
self.button7.center = CGPointMake(self.startingCenter.x, self.startingCenter.y + (testPoint.y - self.startingPoint.y));
break;
default:
break;
}
break;
case UIGestureRecognizerStateEnded:
self.axis = elAxisDefault;
break;
default:
break;
}
}
- (void)panDetected:(UIPanGestureRecognizer *)panGestureRecognizer {
if(panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
// record the point of the touch - decide whether the pan is moving an
// object or misses everything and should be ignored
} else if(panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
// animate the object that might be dragged in the direction you allow, and back,
// if the gesture point moves around a bit
} else if(panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
// if the gesture ends with a net movement in the allowed direction, finalize the
// object's move else restore its original position with an animation back
// to its original place
}
}
This is the kind of thing you want, in the gesture recogniser handler.

How to do smooth swipe animation in ios

I want to do smooth swipe animation. I just want to that swipe only can be possible when user swipe the page from the right or left border only. Middle of the page swipe should not possible.Both the swipe should be possible left to right and right to left.
I have tried lots of swipe animation sample code or demo code. But its not what I want. I want animation like this https://itunes.apple.com/in/app/clear-tasks-to-do-list/id493136154?mt=8
In this app its like when we touch the right border its swipe smoothly.Please guide me to do this animation. Thanks in advance.
Sorry for the late reply. Just saw this question.
If you want your swipe operation to happen from the edges, create 2 subviews in the far ends (left and right) of your main view and give then a width of 30 or 40.
I believe you have 2 other views popin up from left and right. So inorder to do this you need to add 2 views right on top of your main view.
Now for the left view, set it's right horizondal space constraint connecting to the main view to a value lesser than (-1)x width of the main view. For the right view set its right horizondal space constraint connecting to the main view to a value greater than the width of the main view, so that both the views are outside the main view
X stands for a value greater than or equal to the mainview's width
Add two NSLayoutConstraint variables as IBOutlet holding these 2 values.
NSLayoutConstraint *leftViewHorizondalRightPadding;
NSLayoutConstraint *rightViewHorizondalRightPadding;
Now add the UISwipeGestures to these subViews (indicated in orange).
UISwipeGestureRecognizer *leftToRightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[leftToRightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.leftSubview addGestureRecognizer:leftToRightSwipe];
UISwipeGestureRecognizer *rightToLeftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[rightToLeftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.rightSubview addGestureRecognizer:rightToLeftSwipe];
///Now in the swipe handler distinguish the swipe actions
-(void)handleSwipe:(UISwipeGestureRecognizer *)recognizer {
NSLog(#"Swipe received.");
if (recognizer.direction == UISwipeGestureRecognizerDirectionRight) {
//It's leftToRight
leftViewHorizondalRightPadding.constant = 0;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}];
}
else {
//It's rightToLeft
rightViewHorizondalRightPadding.constant = 0;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}];
}
}
}
This will make a swipe animation from left to right and right to left.
Hope this helps..
After you create the 2 swipe gesture recognisers you should set their delegates. Then use this delegate method:
UISwipeGestureRecognizer *_swipeLeft;
UISwipeGestureRecognizer *_swipeRight;
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
static const CGFloat borderWidth = 50.0f;
if(gestureRecognizer == _swipeLeft) {
return [gestureRecognizer locationInView:self].x > self.frame.size.width - borderWidth;
}
else if(gestureRecognizer == _swipeRight) {
return [gestureRecognizer locationInView:self].x < borderWidth;
}
return YES;
}
Do note that for smooth swiping/dragging you will probably need to use a pan gesture or even long press gesture recogniser rather then the swipe gesture. They are very similar except the long press takes a bit of time to begin (which is settable). If you use them you may still want to use the same delegate method. Or you can simply do all the code in the gestures target method. Try something like this:
CGPoint gestureStartPoint;
- (void)dragFromBoreder:(UIGestureRecognizer *)sender {
static const CGFloat borderWidth = 50.0f;
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
CGPoint location = [sender locationInView:self];
if(location.x > borderWidth || location.x < self.frame.size.width-borderWidth) {
//break the gesture
sender.enabled = NO;
sender.enabled = YES;
}
else {
gestureStartPoint = location;
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint location = [sender locationInView:self];
CGFloat deltaX = location.x - gestureStartPoint.x;
UIView *viewToMove;
CGPoint defaultCenter;
viewToMove.center = CGPointMake(defaultCenter.x+deltaX, defaultCenter.y);
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint location = [sender locationInView:self];
CGFloat deltaX = location.x - gestureStartPoint.x;
/*
if(deltaX > someWidth) {
show the left view
}
else if(deltaX < -someWidth) {
show the right view
}
else {
put everything back the way it was
}
*/
break;
}
default:
break;
}
}
In ios7 there is a gesture recogniser specifically for gestures beginning from the edge of the screen. You should use this.
I can't help with your "smooth" problem, because you haven't said what your current animation looks like or how you are doing it. But a pan gesture, like the one linked, which directly updates view positions, will track the user's movement much more smoothly than a swipe.

iOS Animate sliding an imageview from off-screen to on-screen with gesture

I'm looking to animate bubbles with text on them to slide on and off the screen. The ideal implementation for this animation is iOS's horizonatal scroll with paging enabled. I definitely want the "bounce" when I reach the end of the speech bubbles and I definetely want the bubbles to track the finger until a certain point before they will slide off the screen. I believe this is not the same as a swipe (which is just a flick in one direction).
However, the problem with the horizontal scroll is that it is optimized for a static number of images. I will be having a dynamic number of images and as far as I can tell, you cannot dynamically append images to horizontal scroller. The idea is the app dynamically adds content to the scroller as you continue to progress through it.
The scroller was easy enough to get going but I'm going to have to tear it down now. How can I get started with the gesture (I'm not sure if the standard gesture recognizers will work for me at this point) as well as the animation? I've never worked with that portion of iOS code before.
I'm not sure if I follow your question entirely, but if you want to animate the movement of something based upon a gesture, you can use a UIPanGestureRecognizer and change the center of whatever subview you want. For example, in viewDidLoad you would:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(movePiece:)];
[whateverViewYouWantToAnimate addGestureRecognizer:panGesture];
You can then have your gesture recognizer move it where ever you want:
- (void)movePiece:(UIPanGestureRecognizer *)gestureRecognizer
{
static CGPoint originalCenter;
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
originalCenter = [gestureRecognizer view].center;
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gestureRecognizer translationInView:self.view];
gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y);
// if you wanted to animate both left/right and up/down, it would be:
// gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
// replace this offscreen CGPoint with something that makes sense for your app
CGPoint offscreen = CGPointMake(480, gestureRecognizer.view.center.y);
[UIView animateWithDuration:0.5
animations:^{
gestureRecognizer.view.center = offscreen;
}
completion:^(BOOL finished){
// when you're done, you might want to do whatever cleanup
// is appropriate for your app (e.g. do you want to remove it?)
[gestureRecognizer.view removeFromSuperview];
}];
}
}

Resources