How to add a UIPanGestureRecognizer to an shape drawn on CAShapLayer - swift? - ios

I have an imageView that I have drawn a blue circle in its layer. I would like a user to be able to tap, hold and drag this blue circle anywhere within the UIImageView. I am unsure how to attach this shape to a UIPanGestureRecognizer. My effort so far is below:
class DrawCircleViewController: UIViewController {
#IBOutlet weak var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
// ADD GESTURE RECOGNIZER
let panRecgonizer = UIPanGestureRecognizer.init(target: ???, action: <#T##Selector?#>)
}
func drawBlueCircle(){
let fourDotLayer = CAShapeLayer()
fourDotLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 60, y: 60, width: 30, height: 30), cornerRadius: 50).cgPath
fourDotLayer.fillColor = UIColor.blue.cgColor
self.imgView.layer.addSublayer(fourDotLayer)
}
}

use this code to move the view
#objc func handlePanRecgonizer(_ gestureRecognizer: UIPanGestureRecognizer){
if panRecgonizer.state == .began || panRecgonizer.state == .changed {
let translation = panRecgonizer.translation(in: self.view)
panRecgonizer.view!.center = CGPoint(x: panRecgonizer.view!.center.x + translation.x, y: panRecgonizer.view!.center.y + translation.y)
panRecgonizer.setTranslation(CGPoint.zero, in: self.view)
}
}
if you want to add UIPanGestureRecognizer programmatically:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecgonizer))
self.someDraggableView.addGestureRecognizer(gestureRecognizer)

As you might have noticed you can't add GestureRecognizers to CALayers. They can only be added to UIView types.
The solution is to add a subview to your imageview and draw the blue circle in it.
var drawingView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
drawingView.frame = imgView.bounds
imgView.addSubview(drawingView)
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
// ADD GESTURE RECOGNIZER
let panRecgonizer = UIPanGestureRecognizer(target: drawingView, action: #selector())
drawingView.addGestureRecognizer(panRecgonizer)
}
func drawBlueCircle(){
let fourDotLayer = CAShapeLayer()
fourDotLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 60, y: 60, width: 30, height: 30), cornerRadius: 50).cgPath
fourDotLayer.fillColor = UIColor.blue.cgColor
drawingView?.layer.addSublayer(fourDotLayer)
}

Related

Press a button and thereafter be able to touch on screen and drag a hollow rectangle from (startpoint) until you let go (endpoint)

When using a drawing program or when using photoshop there is usually the ability to select a rectangle from the buttons panel. There you can press the button and afterwards be able to drag a rectangle on the screen depending on your chosen startpoint/endpoint.
Class
#IBDesignable class RectView: UIView {
#IBInspectable var startPoint:CGPoint = CGPoint.zero {
didSet{
self.setNeedsDisplay()
}
}
#IBInspectable var endPoint:CGPoint = CGPoint.zero {
didSet{
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
if (startPoint != nil && endPoint != nil){
let path:UIBezierPath = UIBezierPath(rect: CGRect(x: min(startPoint.x, endPoint.x),
y: min(startPoint.y, endPoint.y),
width: abs(startPoint.x - endPoint.x),
height: abs(startPoint.y - endPoint.y)))
UIColor.black.setStroke()
path.lineCapStyle = .round
path.lineWidth = 10
path.stroke()
}
}
}
Top ViewController
+Added class RectView to View(Custom Class)
class ViewController: UIViewController, UIGestureRecognizerDelegate {
let rectView = RectView()
#IBOutlet var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(panGestureMoveAround(sender:)))
tap.delegate = self
myView.addGestureRecognizer(tap)
ViewController
#objc func panGestureMoveAround(sender: UITapGestureRecognizer) {
var locationOfBeganTap: CGPoint
var locationOfEndTap: CGPoint
if sender.state == UIGestureRecognizer.State.began {
locationOfBeganTap = sender.location(in: rectView)
rectView.startPoint = locationOfBeganTap
rectView.endPoint = locationOfBeganTap
} else if sender.state == UIGestureRecognizer.State.ended {
locationOfEndTap = sender.location(in: rectView)
rectView.endPoint = sender.location(in: rectView)
} else{
rectView.endPoint = sender.location(in: rectView)
}
}
Code gives no particular errors however nothing is happening on screen. Any advice would be helpful.
You should try to focus on one issue at a time, however...
1) #IBDesignable and #IBInspectable are for use during design-time - that is, when laying out views in Storyboard / Interface Builder. That's not what you're trying to do here, so remove those.
2) CGrect() needs x, t, width and height:
let path:UIBezierPath = UIBezierPath(rect: CGRect(x: min(startPoint!.x, endPoint!.x),
y: min(startPoint!.y, endPoint!.y),
width: fabs(startPoint!.x - endPoint!.x),
height: fabs(startPoint!.y - endPoint!.y)))
3) Wrong way to instantiate your view:
// wrong
let rectView = RectView.self
// right
let rectView = RectView()
Correct those issue and see where you get. If you're still running into problems, first search for the specific issue. If you can't find the answer from searching, then come back and post a specific question.
Probably worth reviewing How to Ask

Swift: restoring UIView center after moving it

I'm using gestures to zoom and move a UIImageView (like Instagram zoom, for example). When the gesture ends, I want to restore UIImageView initial position, but I cannot get a copy of the initial center because it's a reference type. Using:
let prevCenter = myImage.center
is of course useless. How can I copy it?
On zoom and move apply transform to view. On the end just set .identity value.
Edit
Example:
#IBOutlet weak var butt: UIButton!
var offsetTransform: CGAffineTransform = .identity
var zoomTransform: CGAffineTransform = .identity
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let pan = UIPanGestureRecognizer(target: self, action: #selector(onPan(pan:)))
pan.minimumNumberOfTouches = 2
pan.delegate = self
view.addGestureRecognizer(pan)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(onPinch(pinch:)))
pinch.delegate = self
view.addGestureRecognizer(pinch)
}
#objc func onPinch(pinch: UIPinchGestureRecognizer) {
let s = pinch.scale
zoomTransform = CGAffineTransform(scaleX: s, y: s)
butt.transform = offsetTransform.concatenating(zoomTransform)
if (pinch.state == .ended) {
finish()
}
}
#objc func onPan(pan: UIPanGestureRecognizer) {
let t = pan.translation(in: view)
offsetTransform = CGAffineTransform(translationX: t.x, y: t.y)
}
func updatePos() {
butt.transform = offsetTransform.concatenating(zoomTransform)
}
func finish() {
butt.transform = .identity
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Are you sure that you are not updating the prevCenter somewhere else?
center is struct so you shouldn't have problems copying it.
Just make sure to take it (the center) when the view is in original state, before starting to zoom it and after it is drawn with correct frame
let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
let prevCenter = view.center // 20, 20
imageView.center = CGPoint(x: 50, y: 50)
// imageView.center - 50, 50
// prevCenter - 20, 20
imageView.frame = CGRect(x: 40, y: 40, width: 10, height: 10)
// imageView.center - 45, 45
// prevCenter - 20, 20

Swift: Changing (translate) a UIView position through a pan gesture in its superview window

Introduction
Context:
In my main ViewController I have a scrollView with a few objects inside (which are UIViews). When one of the UIViews are tapped/selected I animate forward a UITextView in a UIView to go with the selected object. (only one UIView can appear at a time)
This UIView that appears on object selection is separated into a separate class called AdjunctiveTextView.
Issue/goal:
(the example code provided below will clear make this clear, I've also commented where the issue lies in the code)
When an object has been tapped and has an adjacent UIView with a text I want to have that adjacent UIView to follow with the scrollView.
I'm using a UIPanGestureRecognizer to attempt to do this. But I can't figure out how to make it work when the user drags in the scrollview. It only work if the user drags on the actual adjunctiveTextView.
Everything works as expected except that the adjunctiveTextView does not change its position during the panGesture.
I would like (if possible) to have the AdjunctiveTextView as a separate class. My ViewController file is getting rather big.
Question:
Why doesn't the UIPanGestureRecognizer work as expected? What is needed in order for it to translate the backView correctly?
Code
My attempt: (as shown below)
My attempt simply makes the backView itself "dragable" around through the panGesture. Nothing happens to it when I scroll the scrollView.
(I have only included relevant portions of my code)
class ViewController: UIViewController {
let adjunctiveTextView = AdjunctiveTextView()
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
** adjunctiveTextView.scrollView = scrollView // **Edited! (scrollView is the name of the scrollView in this class too)
adjunctiveTextView.showView(passInObject: AvailableObject)
}
}
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
}
lazy var textView: UITextView = {
//textView setup
}
//additional init and setup
** weak var scrollView : UIScrollView! // **Edited!
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// window.addGestureRecognizer(panG)
** scrollView.addGestureRecognizer(panG) // **Edited!
window.addSubview(backView)
textView.text = passInObject.information
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window) //Have tried setting this to scrollView also
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window) //Have tried setting this to sccrollView also
break
case .ended:
break
default:
break
}
}
}
}
Thanks for reading my question.
I just add a weak reference to your scrollView and then add the pan gesture to scrollView. It works as you want. You may consider add another pan gesture to the back view if you want your original behavior.
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
return UIView.init()
}()
lazy var textView: UITextView = {
//textView setup
return UITextView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 100))
}()
weak var scrollView: UIScrollView!
//additional init and setup
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// passInObject.addGestureRecognizer(panG)
scrollView.addGestureRecognizer(panG)
window.addSubview(backView)
textView.text = passInObject.information
textView.backgroundColor = UIColor.blue
backView.addSubview(textView)
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.frame.minY, width: window.frame.width - passInObject.frame.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width , y: passInObject.frame.minY , width: window.frame.width - passInObject.frame.maxX - 6, height: self.textView.bounds.height + 5)
self.backView.backgroundColor = UIColor.red
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window)
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window)
break
case .ended:
break
default:
break
}
}
}
}
class ObjectScrollView: UIScrollView{
}
class AvailableObject: UIView{
var information: String!
}
class MySCNViewController: UIViewController {
#IBOutlet weak var oScrollView: ObjectScrollView!
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
adjunctiveTextView.showView(passInObject: object)
}
let adjunctiveTextView = AdjunctiveTextView()
let ao = AvailableObject.init(frame: CGRect.init(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
ao.information = "test"
adjunctiveTextView.scrollView = oScrollView
ao.backgroundColor = UIColor.yellow
}
#IBAction func tap(_ sender: Any?){
scrollViewObjectIsTapped(oScrollView, object: ao)}
}

Unable to get UIPanGesturerRecognizer to work for child UIView of parent UIImageView - swift

I have an application that has a UIImageView that has a subview(UIView) that contains CAShapeLayer which i used to draw a blue circle. I would like the user to be able to drag the blue circle anywhere over the UIImageView. My problem is the following. I have made the CAShapeLayer to be the same CGRect as the parent UIView so that the user has to actually touch the blue circle before be able to drag it around. When I do this, my translations no longer work. However, if i make the the UIView frame the same as the parent UIImagView, the dragging of the blue circle is enabled, but then the user does not necessarily have to be touch the blue circle to be able to drag it around.
class DrawCircleViewController: UIViewController {
var circleSize = CGRect.init(x: 60, y: 80, width: 15, height: 15)
var firstDotView: UIView?
#IBOutlet weak var imgView: UIImageView!
// LAYERS
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
firstDotView = UIView.init()
firstDotView?.frame = circleSize
imgView.addSubview(firstDotView!)
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
imgView.isUserInteractionEnabled = true
firstDotView!.isUserInteractionEnabled = true
// ADD GESTURE RECOGNIZER
let firstDotPanRecgnzr = UIPanGestureRecognizer.init(target: self, action: #selector(firstDotHandler(sender:)))
firstDotView?.addGestureRecognizer(firstDotPanRecgnzr)
}
#objc func firstDotHandler(sender: UIPanGestureRecognizer) -> Void {
let firstDot = sender.view
let translation = sender.translation(in: view)
if(sender.state == .began || sender.state == .changed){
firstDot?.center = CGPoint.init(x: (firstDot?.center.x)! + translation.x, y: (firstDot?.center.y)! + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
func drawBlueCircle(){
layer1.path = UIBezierPath.init(roundedRect: circleSize, cornerRadius: 50).cgPath
layer1.fillColor = UIColor.blue.cgColor
firstDotView?.layer.addSublayer(layer1)
}
}
sceen shot of the UI

UIImages or UIViews are not colliding each other in iOS Swift?

I am trying to collide two UIViews each other for using pan gesture. Now i can able to drag one view but when dragging view to next view its collide each other. It goes behind other view.
Here is the screenshot result:
Here is the code I used to drag, pan and collide of physics property.
import UIKit
class ViewController: UIViewController {
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
var snap: UISnapBehavior!
var panGesture = UIPanGestureRecognizer()
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let tapPoint: CGPoint = sender.location(in: view)
if (snap != nil) {
animator?.removeBehavior(snap)
}
snap = UISnapBehavior(item: greenSquare!, snapTo: tapPoint)
animator?.addBehavior(snap)
}
override func viewDidLoad() {
super.viewDidLoad()
var dimen = CGRect(x: 25, y: 25, width: 120, height: 120)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.green
dimen = CGRect(x: 130, y: 25, width: 100, height: 100)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.red
self.view.addSubview(greenSquare!)
self.view.addSubview(redSquare!)
animator = UIDynamicAnimator(referenceView: self.view)
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!])
let direction = CGVector(dx: 0.0, dy: 1.0)
gravity.gravityDirection = direction
let boundries = UICollisionBehavior(items: [greenSquare!, redSquare!])
boundries.translatesReferenceBoundsIntoBoundary = true
let bounce = UIDynamicItemBehavior(items: [greenSquare!, redSquare!])
bounce.elasticity = 0.5
animator?.addBehavior(bounce)
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
panGesture = UIPanGestureRecognizer(target: self, action:#selector(draggedView(sender:)))
greenSquare?.isUserInteractionEnabled = true
greenSquare?.addGestureRecognizer(panGesture)
}
#objc func draggedView(sender:UIPanGestureRecognizer){
let translation = sender.translation(in: self.view)
greenSquare?.center = CGPoint(x: (greenSquare?.center.x)! + translation.x, y: (greenSquare?.center.y)! + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}//class`
Anyone can help me out this.
The issue is that the animator only recognizes the collision at the origin of the greenSquare. In this case it is at the top of the screen. You can update the collision location on the animator after you move the greenSquare. Add animator?.updateItem(usingCurrentState: greenSquare!)
to the end of the draggedView method. You do not need to use UISnapBehavior for this so you can remove the IBAction func pan method at the top of your code. The updateItem(usingCurrentState:) call will reset where the collision barrier is when you are moving the view manually and it is not being done by the physics engine.

Resources