I am building an iphone app that has multiple UIViews in a 3x3 matrix (so total of 9 UIViews) within one UIViewController. I am trying to find a way to let the user move one view to a location in the matrix which will then rearrange the rest of the views accordingly. Think of when you drag an app on your springboard to another place and all the other icons arrange themselves accordingly.
What is the best way to accomplish something like that?
Use UIPanGestureRecognizer, using the translationInView to adjust the coordinates of the item you're dragging. For a discussion of gesture recognizers, see the Event Handling Guide for iOS.
When you let go (i.e. the gesture ends), you can use UIView class method animateWithDuration to animate the moving of various items to their final locations.
So, in viewDidLoad, you might do something like the following (assuming that you had your nine controls in an array called arrayOfViews):
for (UIView *subview in self.arrayOfViews)
{
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(movePiece:)];
[subview addGestureRecognizer:gesture];
}
And then your gesture recognizer handler might look like:
- (void)movePiece:(UIPanGestureRecognizer *)gesture
{
static CGPoint originalCenter;
if (gesture.state == UIGestureRecognizerStateBegan)
{
originalCenter = gesture.view.center;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gesture translationInView:self.view];
gesture.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
[UIView animateWithDuration:0.25
animations:^{
// move your views to their final resting places here
}];
}
}
This is the bare-bones of what a dragging of controls around might look like.
If you are developing for iOS 6, definitely have a look at the UICollectionView class. If you have dealt with table views before the learning curve should not be too steep. You get the rearranging as well as all the animation for free.
Related
I have googling a while for a solution. But didn't get anything what I needed. later I found a Library that works the same but in a reverse manner and the library is this LNPopupController
My Question is how Can I make a viewcontroller move from top to bottom on swipe Gesture ? Can I tweak this library to achieve it ?
You're looking to implement a UIPanGesture rather than a UISwipeGesture based of the project you linked.
Create a UIPanGestureRecognizer
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(viewDragged:)];
[yourCustomView addGestureRecognizer:panGestureRecognizer];
Then implement the "viewDragged:" method.
heightForTheDraggableRegionLeftInTheView is a value of how much of the view you want to leave on the screen when the user tries to dismiss youCustomView.
- (void) viewDragged: (UIPanGestureRecognizer *) gestureRecognizer {
CGRect viewFrame = gestureRecognizer.view.frame;
CGPoint locationOfTouch = [gestureRecognizer locationInView:self.view];
// Let the user's finger match with the bottom of the NotificationView
[gestureRecognizer.view setFrame:CGRectMake(viewFrame.origin.x, locationOfTouch.y + draggableHeightOfTheNotificationView - viewFrame.size.height, viewFrame.size.width, viewFrame.size.height)];
// If the yourCustomView is being dragged too high, make sure you leave a draggable area so that the user can drag it later
if (locationOfTouch.y >= self.view.frame.size.height - heightForTheDraggableRegionLeftInTheView) {
[gestureRecognizer.view setFrame:CGRectMake(viewFrame.origin.x, 0, viewFrame.size.width, viewFrame.size.height)];
}
// If the NotificationView will go to far below the screen keep it resting at the bottom of the screen
if (locationOfTouch.y <= draggableHeightOfTheNotificationView) {
[gestureRecognizer.view setFrame:CGRectMake(viewFrame.origin.x, draggableHeightOfTheNotificationView * 2 - self.view.frame.size.height, viewFrame.size.width, viewFrame.size.height)];
}
}
I'm trying to detect a touch on a UISubview of a view being animated
Here's my detection code:
//simple but not very elegant way of getting correct frame
CGRect keepFrame = self.fishContainer.layer.frame;
self.fishContainer.layer.frame = [[self.fishContainer.layer presentationLayer] frame];
//get touch location, taking presentation layer into account. (See above)
CGPoint p = [sender locationInView:self.fishContainer];
CALayer *layer =[self.fishContainer.layer presentationLayer];
//apply relevant transform
p = CGPointApplyAffineTransform(p,layer.affineTransform);
EBLog(#"checking point %#",NSStringFromCGPoint(p));
UIView *vToRemove = nil;
//find topmost view containing touched point
for (UIView *v in self.parasites) {
EBLog(#"-BOUND %#",NSStringFromCGRect(v.frame));
if(CGRectContainsPoint(v.frame, p))
{
vToRemove = v;
}
}
//OK, we have a view. Let's remove it.
if(vToRemove)
{
EBLog(#"found one");
[vToRemove removeFromSuperview];
[self.parasites removeObject:vToRemove];
if ([self.parasites count] == 0) {
[self showWinnerScreen];
[self stopGame];
}
}
//restore view frame
self.fishContainer.layer.frame = keepFrame;
Everything works correctly as long as I don't animate parasiteArea parentview.
When I animate parasiteArea's parentview (A CAAnimation consisting of move of the view, scale of the view, and rotate of the view) , the touch is outside the bounds of the expected subview.
UPDATE
I manged to get the detection working in most cases (see code above), by using the presentationLayer property and CGPointApplyAffineTransform. There is however, still some cases where it dosnt work.
I guess I need to translate the touch point to the coordinate space of the CAAnimation.
Or something like that? any suggestions?
I ended up using UIView animateWithDuration instead of CAAninmation. For my purpose the limited animation possibles were enough.
I currently am using a UIScrollView to work as a way of scrolling/ dragging the cursor/ marker of a graph that I am displaying on my app. I use scrollViewDidScroll, etc. to detect the contentoffset's position and manipulate what I display on the screen based on this position.
My question is if it is possible to also detect a user's single tap on the screen and get this tap's position (to then set the contentoffset to this position)?
I have read some tutorials that describe creating a subclass of a uiscrollview, but it seems like these subclasses will ONLY account for a single tap and not drags/scrolls also.
Does anyone have any insight on how I might be able to detect a user's single tap on my UIScrollView while still having its scrolling/ dragging capabilities?
Thank you in advance!
Use the following code for your scrollView object :
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTap:)];
singleTapGestureRecognizer.numberOfTapsRequired = 1;
singleTapGestureRecognizer.enabled = YES;
singleTapGestureRecognizer.cancelsTouchesInView = NO;
[scrollView addGestureRecognizer:singleTapGestureRecognizer];
//[singleTapGestureRecognizer release]; Not needed in ARC-enabled Project
}
- (void)singleTap:(UITapGestureRecognizer *)gesture {
//handle taps
}
Swift 5
let scrollViewTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(scrollViewTapped(_:)))
scrollViewTapGestureRecognizer.numberOfTapsRequired = 1
scrollViewTapGestureRecognizer.isEnabled = true
scrollViewTapGestureRecognizer.cancelsTouchesInView = false
scrollView.addGestureRecognizer(scrollViewTapGestureRecognizer)
#objc func scrollViewTapped(_ sender: UITapGestureRecognizer) {
print("UIScrollView was tapped.")
}
The first part of the question is pretty simple to get going. If you have a UIScrollView subclass, you can just add a touchesEnded override that looks like this:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 1) {
CGPoint location = [touch locationInView:self];
[self setContentOffset:location animated:YES];
}
}
In the case of the scroll view, the -locationInView for the touch will actually already have the ContentOffset added into it. What this code will do is scroll the scroll view so that the point the user touched is at the top-left of the view. This probably isn't what you want exactly, so you'll have to do things like add in an offset if you want the scroll to end up 10 pixels in, or you'd make another CGPoint with only the x value out of the location if your scroller isn't left-to-right.
This won't interfere with being able to actually do the scroll.
Have you looked into attaching UITapGestureRecognizer to the scrollView? I've attached a UITapGestureRecognizer to a UIScrollView and used –gestureRecognizerShouldBegin: and/or –gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: to cancel the built in panning when a touch is desired.
I have a UIButton that i'd like the user to be able to drag with TouchDragInside. How do i get the button to move as the user moves their finger?
As Jamie noted, a pan gesture recognizer is probably the way to go. The code would look something like what follows.
The button's view controller might add a gesture recognizer to the button (possibly in viewDidLoad) as follows:
UIPanGestureRecognizer *pangr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)];
[myButton addGestureRecognizer:pangr];
[pangr release];
And, the view controller would have the following target method to handle the gesture:
- (void)pan:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateChanged ||
recognizer.state == UIGestureRecognizerStateEnded) {
UIView *draggedButton = recognizer.view;
CGPoint translation = [recognizer translationInView:self.view];
CGRect newButtonFrame = draggedButton.frame;
newButtonFrame.origin.x += translation.x;
newButtonFrame.origin.y += translation.y;
draggedButton.frame = newButtonFrame;
[recognizer setTranslation:CGPointZero inView:self.view];
}
}
CORRECTED as per rohan-patel's comment.
In the previously posted code , the x and y coordinate's of the origin of the button's frame were set directly. It was incorrect as: draggedButton.frame.origin.x += translation.x. A view's frame can be changed, but the frame's components cannot be changed directly.
You probably don't want to use TouchDragInside. That is a method of recognizing that a button or other control has been activated in a certain way. To move the button, you probably want to use a UIPanGestureRecognizer and then change the buttons position in its superview as the users finger moves around.
You have to implement these four methods, touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, and touchesCancelled:withEvent: in the view that holds the button. The property you are referring to cannot be used directly to drag any uiview
I have a UIView inside of a UIScrollView, both created using IB. The UIView scrolls horizontally inside the UIScrollView. I want to detect left and right 2 finger swipes.
Borrowing from the sample code I found in SmpleGestureRecognizers, I have put the following code in the viewDidLoad method of the UIScrollView's ViewController...
UIGestureRecognizer *recognizer;
UISwipeGestureRecognizer *swipeRightRecognizer, *swipeLeftRecognizer;
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeFrom:)];
swipeRightRecognizer = (UISwipeGestureRecognizer *)recognizer;
swipeRightRecognizer.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:swipeRightRecognizer];
[recognizer release];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeFrom:)];
swipeLeftRecognizer = (UISwipeGestureRecognizer *)recognizer;
swipeLeftRecognizer.numberOfTouchesRequired = 2;
swipeLeftRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeLeftRecognizer];
[recognizer release];
I have set in the viewcontroller.h. and have the following delegate method...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
I am assuming this is a valid gestureRecognizer delegate method, but I cannot find any reference to it in the documentation.
I do not get any errors but nothing happens when I do a 2 finger swipe. The delegate method is not called and neither is my action method. I tried removing the numbeOfTouchesRequired call to see if it might work with a single finger swipe to no avail.
Am I adding the gestureRecognizers to the right view? I tried adding it to the UIView, the UIScrollView as well as self.view.superView.
The sample code runs great. The only difference I can see between my implementation of the code and the sample code is the fact that I used IB to create the views and the sample code did not. I suspect that something is consuming the swipe gesture before it gets to my recognizers.
What am I doing wrong.
Thanks,
John
I had the same problem and I solved by using UIPanGestureRecognizer instead of UISwipeGestureRecognizer.
To emulate the detection of swipe, we'll play with the speed of gesture in scrollview.
If the speed of x direction >= 3000 (for example) the swipe will be detected.
If x>0 it will be a right swipe.
The code I implemented to resolve your situation is:
In a uiscrollview named _scroll1:
UIPanGestureRecognizer *pan;
pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(Swipe4ScrollViews:)];
[pan setMinimumNumberOfTouches:2];
[_scroll1 addGestureRecognizer:pan];
[pan release];
With a global BOOL variable named _panning, Swipe4ScrollViews will do the hard job:
-(void)Swipe4ScrollViews:(UIPanGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan) _panning = NO;
CGPoint v =[sender velocityInView:_scroll1];
NSLog(#"%f, %f",v.x,v.y);
if( (abs(v.x) >= UMBRAL) && !_panning)
{
_panning = YES;
[sender cancelsTouchesInView];
if(v.x>0) NSLog(#"Right");
else NSLog(#"Left");
[self doSomething];
}
}
I encapsulated it on a UIGestureRecognizer subclass: UISwipe4ScrollGestureRecognizer
The biggest difference between the sample code and your code is that your code involves a UIScrollView.
Internally, scroll views, table views, and web views all use gesture recognizers to some degree. If you're expecting to receive gestures within those views – gestures that are similar to the ones already supported internally – they will almost certainly be consumed or significantly delayed before you can get to them. Receiving gestures outside or above those views should work fine, if your use case supports it.
Try setting the delayContentTouches-property of the UIScrollView to NO, maybe it'll help.