turn off a pangesturderecognizer with a double tapgesturerecognizer and a swift flow - ios

I have a button on my main UI that I can create views programmatically with. That is down at the bottom. They come out as a green circle that I can move around using a PanGestureRecognizer.
My goal is to turn off the PanGestureRecognizer with a TapGestureRecognizer (I would like it to be a double tap) essentially locking that specific view in place once I double tap it.
This is the code that I have so far. When I tap the view it does in fact change color, but I cannot figure out how to reference the PanGestureRecognizer to disable it.
class MakeACircle: UIView {
var circleCenter = CGPoint()
let tap = UITapGestureRecognizer()
let panGesture = UIPanGestureRecognizer()
var checkSomething = 1
init() {
super.init(frame: CGRect(x: 0.0, y: 0.0, width: 100, height: 100))
self.layer.cornerRadius = 50.0
self.backgroundColor = UIColor.green
self.isUserInteractionEnabled = true
UITapGestureRecognizer().numberOfTapsRequired = 2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dragCircle(gesture: UIPanGestureRecognizer) {
let target = gesture.view!
switch gesture.state {
case .began, .ended:
circleCenter = target.center
case .changed:
let translation = gesture.translation(in: self.superview)
target.center = CGPoint(x: circleCenter.x + translation.x, y: circleCenter.y + translation.y)
default: break
}
}
func freeze(gesture: UITapGestureRecognizer) {
switch checkSomething {
case 1:
self.panGesture.isEnabled = false
self.backgroundColor = UIColor.blue
checkSomething += 1
print(checkSomething)
case 2:
self.panGesture.isEnabled = true
self.backgroundColor = UIColor.red
checkSomething -= 1
print(checkSomething)
default:
break
}
}
}
I am creating the view programmatically with this IBButton
#IBAction func makeACircle(_ sender: UIButton) {
let something = MakeACircle()
self.view.addSubview(something)
something.addGestureRecognizer(UIPanGestureRecognizer(target: something, action: #selector(something.dragCircle(gesture:))))
something.addGestureRecognizer(UITapGestureRecognizer(target: something, action: #selector(something.freeze(gesture:))))
something.center = self.view.center
}
The taps work, as you can see I have a switch in there to turn it colors.. it prints the number and turns color.. but it happens with just one tap and the pan stays on.
Any insight is greatly appreciated! This is my first execution of this, I know I'm right at the door, but I just can't figure it out.

It looks like you're not storing a reference to self.panGesture on your MakeACircle() object when you add it, so when you try to disable it, it doesn't do anything. Where is self.panGesture being set?
instead of this:
something.addGestureRecognizer(UIPanGestureRecognizer(target: something, action: #selector(something.dragCircle(gesture:))))
try this:
something.panGesture = UIPanGestureRecognizer(target: something, action: #selector(something.dragCircle(gesture:)))
something.addGestureRecognizer(something.panGesture)
UPDATE
for your tap gesture recognizer, make sure you're also storing a reference to it in the same way you are with the panGesture.
in your init(), you have:
UITapGestureRecognizer().numberOfTapsRequired = 2
but that won't do anything because it's not the same tap gesture you added when you said
something.addGestureRecognizer(UITapGestureRecognizer(...))
So you should do something like you did with the other gesture
something.tap = UITapGestureRecognizer(target: something, action: #selector(whatever))
something.tap.numberOfTapsRequired = 2
something.addGestureRecognizer(something.tap)

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

Sluggish response from custom slider

I am trying to create a custom slider that uses a UIButton as the thumb. Since I can't seem to find a way to use a UIView as the thumb in a slider, I decided to build a custom one. However, when I run it, the thumb is sluggish to respond on the initial drag. The following is the code so far. The gif below is running slower than actual speed but illustrates the point.
import Foundation
import UIKit
class CustomSlider: UIView, UIGestureRecognizerDelegate {
let buttonView = UIButton()
var minimumPosition = CGPoint()
var maximumPosition = CGPoint()
var originalCenter = CGPoint()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
// add a pan recognizer
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:)))
recognizer.delegate = self
addGestureRecognizer(recognizer)
backgroundColor = UIColor.green
buttonView.frame = CGRect(x: 0, y: 0, width: bounds.height, height: bounds.height)
buttonView.backgroundColor = UIColor.purple
self.addSubview(buttonView)
minimumPosition = CGPoint(x:buttonView.frame.width / 2, y: 0)
maximumPosition = CGPoint(x: bounds.width - buttonView.frame.width / 2, y: 0)
}
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
if recognizer.state == .began {
originalCenter = buttonView.center
}
if recognizer.state == .changed {
let translation = recognizer.translation(in: self)
let newX = originalCenter.x + translation.x
buttonView.center = CGPoint(x: min(maximumPosition.x, max(minimumPosition.x, newX)), y: buttonView.frame.midY)
}
}
}
To make it look smoother, you should reset the translation of the pan and just add it continuously to the center offset:
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
if recognizer.state == .began {
// originalCenter = buttonView.center
}
if recognizer.state == .changed {
originalCenter = buttonView.center
let translation = recognizer.translation(in: self)
let newX = originalCenter.x + translation.x
buttonView.center = CGPoint(x: min(maximumPosition.x, max(minimumPosition.x, newX)), y: buttonView.frame.midY)
recognizer.setTranslation(.zero, in: self)
}
}
Also, for making a custom control, you can check out this tutorial:
https://www.sitepoint.com/wicked-ios-range-slider-part-one/
It might be old and in objective-c, but it gives the general idea of overriding UIControl, handling touches and creating a custom control - which you can also extend with #IBDesignable and #IBInspectable.
Hope it helps.
Finally figured it out. It only happens in the iPhone XR simulator. Tried the iPhone XS, among others, and they were smooth as glass.

Dragging UIView on touch not working when finger is on UIButton inside that View

I want to be able to move around UIView with finger.
Inside UIView is UIButton, when finger starts dragging from area inside view but outside of button everything works as expected, but when finger starts dragging from UIButton view is not moving.
So basiclly I need something like delaysContentTouches available for UIScrollView.
How can I stop UIButton from stealing touch? Note that I would like UIButton to be still clickable.
Sample code to represent the problem:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testView = View(frame: CGRect(x: 10.0, y: 10.0, width: 200.0, height: 200.0))
testView.backgroundColor = .blue
view.addSubview(testView)
let button = UIButton()
testView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("button", for: .normal)
button.backgroundColor = .red
button.leadingAnchor.constraint(equalTo: testView.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: testView.trailingAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
button.centerYAnchor.constraint(equalTo: testView.centerYAnchor).isActive = true
}
}
class View: UIView {
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
guard let touch = touches.first else { return }
let touchPoint = touch.location(in: self)
self.frame.origin.x += touchPoint.x
self.frame.origin.y += touchPoint.y
}
}
Why are you using touchesMoved. Use a pan gesture. Thats what they are for. Also, I was able to get button touches as well
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
if let thisView = recognizer.view {
if recognizer.state == .began {
someCentre = thisView.center // store old button center
} else if recognizer.state == .failed || recognizer.state == .cancelled {
thisView.center = someCentre! // restore button center
} else {
let location = recognizer.location(in: view) // get pan location
thisView.center = location // set button to where finger is
}
}}
In view did load -
let panGesture = UIPanGestureRecognizer(target: self, action: selector(self.panGestureHandler(panGesture:)))
panGesture.minimumNumberOfTouches = 1
testView.addGestureRecognizer(panGesture)
someCentre = testView.center

Draggable UIView Swift 3

I want to be able to drag the objects on the screen, but they wont. I tried everything but still cant.
Here are the code.
func panGesture(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
print("Began.")
for i in 0..<forms.count {
if forms[i].frame.contains(gesture.location(in: view)) {
gravity.removeItem(forms[i])
}
}
case .changed:
let translation = gesture.translation(in: forms[1])
gesture.view!.center = CGPoint(x: gesture.view!.center.x + translation.x, y: gesture.view!.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: self.view)
print("\(gesture.view!.center.x)=\(gesture.view!.center.y)")
print("t;: \(translation)")
case .ended:
for i in 0..<forms.count {
if forms[i].frame.contains(gesture.location(in: view)) {
gravity.addItem(forms[i])
}
}
print("Ended.")
case .cancelled:
print("Cancelled")
default:
print("Default")
}
}
Also they have gravity. The forms are squares and circles.
Explanation:
in .began - i disable the gravity for selected form.
in .changed - i try to change the coordinates.
in .end - i enable again gravity.
ScreenShot.
Step 1 : Take one View which you want to drag in storyBoard.
#IBOutlet weak var viewDrag: UIView!
Step 2 : Add PanGesture.
var panGesture = UIPanGestureRecognizer()
Step 3 : In ViewDidLoad adding the below code.
override func viewDidLoad() {
super.viewDidLoad()
panGesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.draggedView(_:)))
viewDrag.isUserInteractionEnabled = true
viewDrag.addGestureRecognizer(panGesture)
}
Step 4 : Code for draggedView.
func draggedView(_ sender:UIPanGestureRecognizer){
self.view.bringSubview(toFront: viewDrag)
let translation = sender.translation(in: self.view)
viewDrag.center = CGPoint(x: viewDrag.center.x + translation.x, y: viewDrag.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
Step 5 : Output.
It's very easy if you subclass a view:
DraggableView...
class DraggableView: UIIView {
var fromleft: NSLayoutConstraint!
var fromtop: NSLayoutConstraint!
override func didMoveToWindow() {
super.didMoveToWindow()
if window != nil {
fromleft = constraint(id: "fromleft")!
fromtop = constraint(id: "fromtop")!
}
}
override func common() {
super.common()
let p = UIPanGestureRecognizer(
target: self, action: #selector(drag))
addGestureRecognizer(p)
}
#objc func drag(_ s:UIPanGestureRecognizer) {
let t = s.translation(in: self.superview)
fromleft.constant = fromleft.constant + t.x
fromtop.constant = fromtop.constant + t.y
s.setTranslation(CGPoint.zero, in: self.superview)
}
}
Drop a UIView in your scene.
As normal, add a constraint from the left (that's the x position) and add a constraint from the top (that's the y position).
In storyboard simply simply name the constraints "fromleft" and "fromtop"
You're done.
It now works perfectly - that's it.
What is that handy constraint( call ?
Notice the view simply finds its own constraints by name.
In Xcode there is STILL no way to use constraints like IBOutlets. Fortunately it is very easy to find them by "identifier". (Indeed, this is the very purpose of the .identifier feature on constraints.)
extension UIView {
func constraint(id: String) -> NSLayoutConstraint? {
let cc = self.allConstraints()
for c in cc { if c.identifier == id { return c } }
//print("someone forgot to label constraint \(id)") //heh!
return nil
}
func allConstraints() -> [NSLayoutConstraint] {
var views = [self]
var view = self
while let superview = view.superview {
views.append(superview)
view = superview
}
return views.flatMap({ $0.constraints }).filter { c in
return c.firstItem as? UIView == self ||
c.secondItem as? UIView == self
}
}
Tip...edge versus center!
Don't forget when you make the constraints on a view (as in the image above):
you can set the left one to be either:
to the left edge of the white box, or,
to the center of the white box.
Choose the correct one for your situation. It will make it much easier to do calculations, set sliders, etc.
Footnote - an "initializing" UIView, UI "I" View,
// UI "I" View ... "I" for initializing
// Simply saves you typing inits everywhere
import UIKit
class UIIView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
func common() { }
}
Use below code for Swift 5.0
Step 1 : Take one UIView from Storyboard, drag it into your ViewController file and Create IBOutlet of UIView.
#IBOutlet weak var viewDrag: UIView!
var panGesture = UIPanGestureRecognizer()
Step 2 : In viewDidLoad() adding the below code.
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action:(Selector(("draggedView:"))))
viewDrag.isUserInteractionEnabled = true
viewDrag.addGestureRecognizer(panGesture)
}
Step 3 : Create func and add code to move the UIView as like below.
func draggedView(sender:UIPanGestureRecognizer){
self.view.bringSubviewToFront(viewDrag)
let translation = sender.translation(in: self.view)
viewDrag.center = CGPoint(x: viewDrag.center.x + translation.x, y: viewDrag.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
Hope this will help someone.
This UIView extension makes a UIView object draggable and limits the movement to stay within the bounds of the screen.
extension UIView {
func makeDraggable() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
self.addGestureRecognizer(panGesture)
}
#objc func handlePan(_ gesture: UIPanGestureRecognizer) {
guard gesture.view != nil else { return }
let translation = gesture.translation(in: gesture.view?.superview)
var newX = gesture.view!.center.x + translation.x
var newY = gesture.view!.center.y + translation.y
let halfWidth = gesture.view!.bounds.width / 2.0
let halfHeight = gesture.view!.bounds.height / 2.0
// Limit the movement to stay within the bounds of the screen
newX = max(halfWidth, newX)
newX = min(UIScreen.main.bounds.width - halfWidth, newX)
newY = max(halfHeight, newY)
newY = min(UIScreen.main.bounds.height - halfHeight, newY)
gesture.view?.center = CGPoint(x: newX, y: newY)
gesture.setTranslation(CGPoint.zero, in: gesture.view?.superview)
}
}

Drag and drop - UIGestureRecognizer - View added not where I want to

I have this 2 tableViews (All Services available and service offered) in a splitViewController. The idea is that then the cell is moved across the center of the screen, it moves to the service offered table view.
Everything works except then the touch `.began, the cell is being added to the top of the tableView covering the navBar.
On .changed it follows my finger
I would like the view/cell to be added where the touch began, on top of the "ServiceCell" I am touching. I tried adding it to the splitViewController.view but I think there is something wrong with my logic.
Here the code:
func didLongPressCell (gr: UILongPressGestureRecognizer) {
let serviceCell:ServiceCell = gr.view as! ServiceCell
switch gr.state {
case .began:
let touchOffsetInCell = gr.location(in: gr.view)
let dragEvent = DragganbleServiceCell()
mDraggableServiceCell = dragEvent.makeWithServiceCell(serviceCell: serviceCell, offset: self.tableView.contentOffset, touchOffset: touchOffsetInCell)
self.splitViewController?.view.addSubview(mDraggableServiceCell!)
case .changed:
let cp:CGPoint = gr.location(in: self.view)
let newOrigin = CGPoint(x: (cp.x), y: (cp.y) - (mDraggableServiceCell?.touchOffset?.y)!)
UIView.animate(withDuration: 0.1, animations: {
self.mDraggableServiceCell?.frame = CGRect(origin: newOrigin, size: (self.mDraggableServiceCell?.frame.size)!)
})
case .ended:
let detailViewNC = self.splitViewController?.viewControllers[1] as! UINavigationController
let detailView = detailViewNC.topViewController as! ServiceOfferedTableViewController
if (mDraggableServiceCell?.frame.intersects(detailView.tableView.frame))! {
onDragEnded(serviceCell:serviceCell)
}else{
mDraggableServiceCell?.removeFromSuperview()
}
default:
print("Any other action?")
}
}
In DraggableServiceCell:
class DragganbleServiceCell: ServiceCell {
var originalPosition:CGPoint?
var touchOffset:CGPoint?
func makeWithServiceCell(serviceCell:ServiceCell, offset:CGPoint, touchOffset:CGPoint)->DragganbleServiceCell{
let newFrame = CGRect(x: serviceCell.frame.origin.x, y: serviceCell.frame.origin.y, width: serviceCell.frame.size.width, height: serviceCell.frame.size.height)
let dragCell = DragganbleServiceCell(frame: newFrame)
dragCell.touchOffset = touchOffset
dragCell.service = serviceCell.service
dragCell.serviceName.text = serviceCell.service?.service_description
return dragCell
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.containingView.backgroundColor = UIColor.rgb(230, green: 32, blue: 31)
self.serviceName.textColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Your problem is in makeWithServiceCell(serviceCell:ServiceCell, offset:CGPoint, touchOffset:CGPoint) -> DragganbleServiceCell. The frame of your serviceCell is relative to the tableView that contains it instead of UISplitViewController's view and you are never using the offset or touchOffset values to correct that. So try adjusting the frame in the same way you are doing it in the .changed event.
You might even find that the convert(_:to:) method on UIView might be useful to do CGPoint transformations between two views. See more details here.
I would suggest adding the gesture to self.splitViewController?.view instead of self.viewif you are not doing and changing this part of the code.
case .changed:
let cp:CGPoint = gr.location(in: self.view)
let newOrigin = CGPoint(x: (cp.x), y: (cp.y) - (mDraggableServiceCell?.touchOffset?.y)!)
UIView.animate(withDuration: 0.1, animations: {
self.mDraggableServiceCell?.frame = CGRect(origin: newOrigin, size: (self.mDraggableServiceCell?.frame.size)!)
})
to this
case .changed:
let cp:CGPoint = gr.location(in: self.splitViewController?.view)
UIView.animate(withDuration: 0.1, animations: {
self.mDraggableServiceCell?.center = cp
})
try once.

Resources