Use Uipangesturerecognizer to Drag Uiview Inside Limited Area

Drag a CGRect using UIPanGestureRecognizer

Try like this (check comments through code):

@IBDesignable
class Rectangle: UIView {

@IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
@objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}

extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}

To draw rectangles on your view when panning you can do as follow:

import UIKit

class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
@objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}

Sample Project

Change alpha of UIView inside UIPanGestureRecognizer change state

Here is solution what I successfully execute for this condition:

 case .changed:
let translation = sender.translation(in: self.view)
sender.view!.center = CGPoint(x: sender.view!.center.x + translation.x, y: sender.view!.center.y)
sender.setTranslation(CGPoint(x: 0, y: 0), in: self.view)

let percentage = (sender.view!.center.x*100.0 / self.view.center.x)/100.0

let alpha = percentage >= 1.0 ? (1.0 - (percentage-1.0)) : percentage

sender.view!.alpha = alpha
cards[1].imageView.alpha = previousCardAlpha + (1.0 - alpha) // previousCardAlpha == cards[1].alpha eg: 0.5

break

This code is working fine in XCode 10 with swift 4.2. Hope this will work for you.

UIPanGesture set boundary

You can do this with constraints - which is simpler and more flexible.

This code will create a 100 x 100 blue view in the center of the view controller, allow it to be dragged (via PanGesture), and will restrict its vertical movement to 200-pts from the top of the view (and 20-pts from the bottom, so you can't drag it down off the screen).

The key is to use a centerYAnchor constraint with a priority of 750 (.defaultHigh), along with >= topAnchor and <= bottomAnchor constraints to limit the vertical positioning.

//
// PanLimitViewController.swift
//
// Created by Don Mag on 8/1/18.
//

import UIKit

class PanLimitViewController: UIViewController {

// blue view for panning (dragging)
var panView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .blue
return v
}()

// the panView's center y constraint
var panCenterYConstraint: NSLayoutConstraint!

// var to track the current center y
var currentCenterYConstant: CGFloat = 0.0

// pan gesture recognizer
var panGesture = UIPanGestureRecognizer()

override func viewDidLoad() {
super.viewDidLoad()

// add the panView
view.addSubview(panView)

// constrain width to 100, height equal to width
panView.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
panView.heightAnchor.constraint(equalTo: panView.widthAnchor).isActive = true

// constrain to center horizontally
panView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

// initialize panView's center y constraint to center of view
panCenterYConstraint = NSLayoutConstraint(
item: panView,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 1.0,
constant: 0.0)

// set panView's center y constraint priority to 750
// required to allow dragging
panCenterYConstraint.priority = .defaultHigh

// activate panView's center y constraint
panCenterYConstraint.isActive = true

// set panView's topAnchor to >= view's top + 200
// this will prevent dragging it higher than 200 pts from the top
panView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 200.0).isActive = true

// set panView's bottomAnchor to <= view's bottom
// this will prevent dragging it below the bottom
panView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -20.0).isActive = true

// create pan gesture and add to panView
panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(_:)))
panView.addGestureRecognizer(panGesture)

}

@objc func panGestureHandler(_ recognizer: UIPanGestureRecognizer){

switch recognizer.state {
case .began:
// save the current center y constant value
currentCenterYConstant = panCenterYConstraint.constant
break

case .changed:
// update panView's center y, based on drag
let translation = recognizer.translation(in: self.view)
panCenterYConstraint.constant = currentCenterYConstant + translation.y
break

case .ended, .cancelled:
// update panView's center y when drag is finished
panCenterYConstraint.constant = panView.center.y - view.center.y
break

default:
break
}

}

}

Limited Area for PanGestureRecognizer

You can implement the delegate methods for the UIPanGestureRecognizer. Check to see if the location of the gesture is in the bounds you are interested in. For the should* methods you can return false to cancel the gesture. Once the gesture has been started you can cancel it by setting the State property to Cancelled.

public class GestureView: UIView
{
RectangleF _bounds;

public GestureView (RectangleF rect) : base (rect)
{
this.BackgroundColor = UIColor.Brown;

UIPanGestureRecognizer pan = new UIPanGestureRecognizer (this, new Selector ("panViewWithGestureRecognizer:"));
this.AddGestureRecognizer (pan);
pan.WeakDelegate = this;
_bounds = new RectangleF (0,0,200, 100);
}

[Export("panViewWithGestureRecognizer:")]
void PanGestureMoveAround (UIPanGestureRecognizer p)
{
if (_bounds.Contains (p.LocationInView (this)))
{
Console.WriteLine ("PanGestureMoveAround true");
return;
}
Console.WriteLine ("PanGestureMoveAround false");
p.State = UIGestureRecognizerState.Cancelled;
return;
}

[Export ("gestureRecognizerShouldBegin:")]
bool ShouldBegin (UIGestureRecognizer recognizer)
{
if (_bounds.Contains (recognizer.LocationInView (recognizer.View)))
{
Console.WriteLine ("ShouldBegin true");
return true;
}
Console.WriteLine ("ShouldBegin false");
return false;
}

[Export ("gestureRecognizer:shouldReceiveTouch:")]
public bool ShouldReceiveTouch (UIGestureRecognizer recognizer, UITouch touch)
{
if (_bounds.Contains (touch.LocationInView (recognizer.View)))
{
Console.WriteLine ("ShouldReceiveTouch true");
return true;
}
Console.WriteLine ("ShouldReceiveTouch false");
return false;
}
}

UIPanGestureRecognizer for programmatically added UIView

No need to create panGesture for updating position. You can get same effect using long press gesture. (I change little bit of the above code, especially view frame while creating).

var circleView: UIView!

@IBAction func spaceLongPressed(_ guesture: UILongPressGestureRecognizer) {
let location = guesture.location(in: view)
let size = CGSize(width: 100, height: 100)
circleView = UIView(frame: CGRect(origin: location, size: size))

if guesture.state == .began {
circleView.backgroundColor = UIColor.red
circleView.isUserInteractionEnabled = true
circleView.tag = 100
circleView.layer.cornerRadius = circleView.bounds.height/2
view.addSubview(circleView)
}

if guesture.state == .changed {
let position = guesture.location(in: view)
if let viewWithTag = self.view.viewWithTag(100) {
viewWithTag.center = position
}
}

if guesture.state == .ended {
if let viewWithTag = self.view.viewWithTag(100) {
viewWithTag.removeFromSuperview()
}
}
}

Output:

abc

Set limit to draggable item in Swift

The main problem is that you are checking the position before you do the translation. That means that the text field ends up in an invalid position, after which the if block is never reached.

This is a slightly different way to approach the problem and means that the text field reaches right to the edges of the limit:

func draggedView(_ sender: UIPanGestureRecognizer) {

guard let senderView = sender.view else {
return
}

var translation = sender.translation(in: view)

translation.x = max(translation.x, MyimageView.frame.minX - bottomTextField.frame.minX)
translation.x = min(translation.x, MyimageView.frame.maxX - bottomTextField.frame.maxX)

translation.y = max(translation.y, MyimageView.frame.minY - bottomTextField.frame.minY)
translation.y = min(translation.y, MyimageView.frame.maxY - bottomTextField.frame.maxY)

senderView.center = CGPoint(x: senderView.center.x + translation.x, y: senderView.center.y + translation.y)
sender.setTranslation(.zero, in: view)
view.bringSubview(toFront: senderView)
}

I have also made it a bit safer by adding a guard statement at the top and removing the force unwraps.



Related Topics



Leave a reply



Submit