So I found out how to make a button draggable using the UIPanGestureRecognizer. But the only way I know how to do it is by storing and dragging the button by the center. The problem with this is if you try and drag the button from a corner, the button instantly shifts from the corner to the center. What I'm looking for is a solution that would keep my finger on a selected place while moving without instantly locking onto the center.
The code I'm currently using:
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonCenter = button.center // store old button center
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
Thanks in advance.
This can be done at least in two different ways, one using GestureRecognizer your question way and other way is subclassing the UIView and implementing the touchesBegan, touchesMoved , touchesEnded, touchesCancelled in general will work for any UIView subclass can be UIButton or UILabel or UIImageView etc...
In your way, using GestureRecognizer I make a few changes you still require a var to keep the origin CGPoint of the touch in your UIButton so we get the touch position relative to the UIButton and when the drag continue adjust the UIButton origin according to the origin touch position and the positions of the movement
Method 1 GestureRecognizer
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
var buttonOrigin : CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let gesture = UIPanGestureRecognizer(target: self, action: #selector(buttonDrag(pan:)))
self.button.addGestureRecognizer(gesture)
}
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonOrigin = pan.location(in: button)
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.frame.origin = CGPoint(x: location.x - buttonOrigin.x, y: location.y - buttonOrigin.y)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Method 2 UIView subclass in this case UIButton subclass
Use this UIButton subclass
import UIKit
class DraggableButton: UIButton {
var localTouchPosition : CGPoint?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.localTouchPosition = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
self.localTouchPosition = nil
}
}
Result
Hope this helps you
Related
Update[11/1/2018]:
I try to test the code step by step and found that:
The method touchesEnded was never called for the leftover UIView.
Same for the method touchesCancelled
I am following the Apple Documentation on implementing a multitouch app.
The url is :
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_touches_in_your_view/implementing_a_multitouch_app
I am using the 4 methods:
- touchesBegan(:with:),
- touchesMoved(:with:),
- touchesEnded(:with:),
- touchesCancelled(:with:)
to create a UIView on the touching location when touchesBegan is called. Update the UIView location when touchesMoved is called. And finally, remove the UIView when touchesEnded and touchedCancelled is called.
The problem is when I am tapping really fast on the screen, the UIView doesn't remove from the superview. The code is basically the same as the code provided in the URL with different graphics. I add another view which offset a little bit from the touch location to have a better look on the touch point.
Example of the leftover UIView
Another Image, same code as the comment below
I don't understand what's wrong and why is this happening. I suspect that touchesEnded() was never called but I don't know why. And I would like to ask is there any method to prevent this from happening (no leftover UIView)?
I am using XCode Version 9.2 (9C40b) and iPad Air 2.
I have try the app on different iPad model and same thing happen.
Part of the codes:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
createViewForTouch(touch: touch)
}
}
func createViewForTouch( touch : UITouch ) {
let newView = TouchSpotView() //TouchSportView is a custom UIView
newView.bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
newView.center = touch.location(in: self)
// Add the view and animate it to a new size.
addSubview(newView)
UIView.animate(withDuration: 0.2) {
newView.bounds.size = CGSize(width: 100, height: 100)
}
// Save the views internally
touchViews[touch] = newView //touchViews is a dict, i.e. touchView[UITouch:TouchSportView]
}
func removeViewForTouch (touch : UITouch ) {
if let view = touchViews[touch] {
view.removeFromSuperview()
touchViews.removeValue(forKey: touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
For the full code, please see the attached URL for apple documentation.
Many thanks for your generous help!!!!
Tried following code (copy pasted from the link you provided) and it works perfectly both on actual device and simulator:
import UIKit
class ViewController: UIViewController {
override func loadView() {
view = TouchableView()
view.backgroundColor = .white
}
}
class TouchableView: UIView {
var touchViews = [UITouch:TouchSpotView]()
override init(frame: CGRect) {
super.init(frame: frame)
isMultipleTouchEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isMultipleTouchEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
createViewForTouch(touch: touch)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let view = viewForTouch(touch: touch)
// Move the view to the new location.
let newLocation = touch.location(in: self)
view?.center = newLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
func createViewForTouch( touch : UITouch ) {
let newView = TouchSpotView()
newView.bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
newView.center = touch.location(in: self)
// Add the view and animate it to a new size.
addSubview(newView)
UIView.animate(withDuration: 0.2) {
newView.bounds.size = CGSize(width: 100, height: 100)
}
// Save the views internally
touchViews[touch] = newView
}
func viewForTouch (touch : UITouch) -> TouchSpotView? {
return touchViews[touch]
}
func removeViewForTouch (touch : UITouch ) {
if let view = touchViews[touch] {
view.removeFromSuperview()
touchViews.removeValue(forKey: touch)
}
}
}
class TouchSpotView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.lightGray
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Update the corner radius when the bounds change.
override var bounds: CGRect {
get { return super.bounds }
set(newBounds) {
super.bounds = newBounds
layer.cornerRadius = newBounds.size.width / 2.0
}
}
}
So, if you are doing something more with it, add that additional code to the question, or if you removed something from the code, let us know. Otherwise, it should work.
EDIT
For completeness I am including a GitHub link to a minimal project, that works for me.
Instead of finding why touchesCancelled and touchesEnded were not called, using event?.allTouches method to check all touch which UITouch is not being handled.
I have a UILabel object which is being animated, moving up and down. I am coding in Xcode 8.3 with Swift 3. A user can pan this object and drag around to one location to get some points. I am handling the pan/drag using touchesXXX gesture. When I tap and let it go immediately, however, this object jumps vertically above its location for some reason and I am unable to figure this out why for a week now...
When I enabled some debugging, I can only see touchesBegan was invoked (touchesMoved was not called as expected, neither touchesEnded which appears unexpected to me). If I disable animation on the object manually, it works as expected and the object is able to be panned effortlessly and there is obviously no object jumps.
Here is an extract of the relevant code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self.view)
if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
//pauseLayer(self.optOneLbl.layer) <<<<< comment #1
//optOneLbl.translatesAutoresizingMaskIntoConstraints = true <<<<< comment #2
optOneLbl.center = touchLocation
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self.view)
var sender: UILabel
if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
self.optOneLbl.center = touchLocation
sender = self.optOneLbl
}
// identify which letter was overlapped
var overlappedView : UILabel
if (sender.frame.contains(letter1.center)) {
...
}
else {
return
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
}
After reading responses to other SO questions, I thought disabling the label animation programmatically when touched as per above comment #1 in touchesBegan might help, but the issue persisted. Also, I thought may be Auto Layout is causing this weird jump. So, I enabled translatesAutoresizingMaskIntoConstraints as per comment #2, but it too didn't help.
Anyone is able to see where I am handling this incorrectly?
Thank you for reading!
EDIT:
As per #agibson007 request, I am adding the animation code extract for reference:
UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y: screenSize.midY*1.1)
})
You need to remove/reset the animation after you change the location of the label. The animation does not know you updated the values and is trying to stay in the same range of byValue from the beginning. Here is a working example of updating the animation.
import UIKit
class ViewController: UIViewController {
var optOneLbl = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
optOneLbl = UILabel(frame: CGRect(x: 20, y: 50, width: self.view.bounds.width - 40, height: 40))
optOneLbl.textAlignment = .center
optOneLbl.text = "I Will Be Moving Up and Down"
optOneLbl.textColor = .white
optOneLbl.backgroundColor = .blue
self.view.addSubview(optOneLbl)
//starts it in the beginnning with a y
fireAnimation(toY: self.view.bounds.midY*1.1)
}
func fireAnimation(toY:CGFloat) {
UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y:toY)
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self.view)
if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
//re
optOneLbl.layer.removeAllAnimations()
optOneLbl.center = touchLocation
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self.view)
var sender: UILabel
if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
self.optOneLbl.center = touchLocation
sender = self.optOneLbl
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
//restart animation after finished and change the Y if you want.
// you could change duration or whatever
fireAnimation(toY: self.view.bounds.height - optOneLbl.bounds.height)
}
}
You can achieve the same using UIPanGestureRecognizer. Add pan gesture to your label and move the label on pan as
#IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
yourLabel!.center = CGPoint(x: yourLabel!.center.x + translation.x, y: yourLabel!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}
}
You can add gesturerecognizer to your UILabel from storyboard or programmatically in viewDidLoad as
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
yourLabel.addGestureRecognizer(gestureRecognizer)
Using Pan gesture as advantage that you dont have to follow up touchesXX method. To why your view is moving up, I think you might be resetting your label frame some where else too. There are also chances for gesture conflicting with touches. As both the cases are not evident in your code provided we need more code to confirm the same.
I can move a single UIView using the code below but how do I move multiple UIView's individually using IBOutletCollection and tag values?
class TeamSelection: UIViewController {
var location = CGPoint(x: 0, y: 0)
#IBOutlet weak var ViewTest: UIView! // move a single image
#IBOutlet var Player: [UIView]! // collection to enable different images with only one outlet
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first! as UITouch
location = touch.location(in: self.view)
ViewTest.center = location
}
}
There are two basic approaches:
You could iterate through your subviews figuring out in which one the touch intersected and move it. But this approach (nor the use of cryptic tag numeric values to identify the view) is generally not the preferred method.
Personally, I'd put the drag logic in the subview itself:
class CustomView: UIView { // or subclass `UIImageView`, as needed
private var originalCenter: CGPoint?
private var dragStart: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
originalCenter = center
dragStart = touches.first!.location(in: superview)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
var location = touch.location(in: superview)
if let predicted = event?.predictedTouches(for: touch)?.last {
location = predicted.location(in: superview)
}
center = dragStart! + location - originalCenter!
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: superview)
center = dragStart! + location - originalCenter!
}
}
extension CGPoint {
static func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
}
Remember to set "user interaction enabled" for the subviews if you use this approach.
By the way, if you're dragging views like this, make sure you don't have constraints on those views or else when the auto-layout engine next applies itself, everything will move back to the original location. If using auto-layout, you'd generally modify the constant of the constraint.
A couple of observations on that dragging logic:
You might want to use predictive touches, like above, to reduce lagginess of the drag.
Rather than moving the center to the location(in:) of the touch, I would rather keep track of by how much I dragged it and either move the center accordingly or apply a corresponding translation. It's a nicer UX, IMHO, because if you grab the the corner, it lets you drag by the corner rather than having it jump the center of the view to where the touch was on screen.
I'd create a subclass of UIView which supports dragging. Something like:
class DraggableView: UIView {
func setDragGesture() {
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(DraggableView.handlePanGesture(_:)))
addGestureRecognizer(panRecognizer)
}
func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
guard let parentView = self.superview else { return }
let translation = recognizer.translation(in: parentView)
recognizer.view?.center = CGPoint(x: recognizer.view!.center.x + translation.x, y: recognizer.view!.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: self)
}
func getLocation() -> CGPoint {
return UIView().convert(center, to: self.superview)
}
}
So then you can add an array of draggable views and then ask for the location when you need to finish displaying that view controller.
I have a UITableView with UITableViewCell like this:
I added a UITapGestureRecognizer (let's call the target targetCell) on the tableView to manage when the user tap on a cell. On the Button, let's call the target targetButton.
On top of the cell, I added a UIScrollView as an overlay.
What I would like to do it that when there is a pan/swipe anywhere on the cell/scrollView, then the pan is detected.
But if there is a tap on the button then targerButton is triggered, if it's outside targetCell is triggered.
So far, only targetCell is triggered, inside or outside of the button area. If I remove the UIScrollView, it works fine.
So at first I thought about overriding func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool. The problem is that at this stage, it doesn't make the difference between a tap and a pan so it doesn't solve my problem.
Then I thought about overriding func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) like the following:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let location = touches.first?.locationInView(tableView) {
var buttonFrame = CGRectMake(frame.width - 50, 0, 50, bounds.height)
buttonFrame.origin.y = (convertRect(buttonFrame, toView: tableView).minY)
let toNextResponder = buttonFrame.contains(location)
if toNextResponder {
self.nextResponder()?.touchesBegan(touches, withEvent: event)
} else {
super.touchesBegan(touches, withEvent: event)
}
} else {
super.touchesBegan(touches, withEvent: event)
}
}
It didn't work. Then I realised that self.nextResponder()? was the UITableViewCellContentView so I thought maybe, call touchesBegan(touches, withEvent: event)directly on the button. So I changed touchesBegan to this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let location = touches.first?.locationInView(tableView) {
var buttonFrame = CGRectMake(frame.width - 50, 0, 50, bounds.height)
buttonFrame.origin.y = (convertRect(buttonFrame, toView: tableView).minY)
let toNextResponder = buttonFrame.contains(location)
if toNextResponder {
if let cellContentView = self.nextResponder() as? UIView {
let button = findButtonInView(cellContentView)
button?.touchesBegan(touches, withEvent: event)
}
} else {
super.touchesBegan(touches, withEvent: event)
}
} else {
super.touchesBegan(touches, withEvent: event)
}
}
private func findButtonInView(view: UIView) -> UIButton? {
if view is UIButton {
return view as? UIButton
} else {
var button: UIButton?
for subview in view.subviews {
button = findButtonInView(subview)
if let _ = button {
return button
}
}
return button
}
}
And... it doesn't work, targetCell only gets triggered whether you tap on the button or not. I noticed something though it that when I call chartButton?.touchInside I get false.
What did I do wrong? Or what should I have done?
Well I found a solution but I don't really like it as it feels more like a hack than a proper one.
I basically execute the button's target when a tap gesture is recognized within the button's area:
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
let location = (gestureRecognizer as! UITapGestureRecognizer).locationInView(tableView)
var buttonFrame = CGRectMake(frame.width - 50, 0, 50, bounds.height)
buttonFrame.origin.y = (convertRect(buttonFrame, toView: tableView).minY)
if buttonFrame.contains(location) {
button.sendActionsForControlEvents(.TouchUpInside)
return false
}
}
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
If anyone has a better way or think my way is wrong please let me know.
I'm making a calculator app that has several UIButtons for input of digits etc. I want the user to be able to touch down on one button and, if this was not the intended button, move the finger to another button and touch up inside that one. The button that the user has his/her finger on should change background color to indicate to the user what is happening, much like Apples built in calculator app.
I've tried to do this by using touch drag inside/outside and touch drag enter/exit on the buttons, but it only works for the button where the touch originated. Meaning I can touch down on one button, drag outside, back inside and touch up inside, but I can't touch down, drag outside and touch up inside another button.
Also the area that is recognized as being inside or outside the button is larger than the bounds of the button.
Here's an example of the code I've tried for one of the buttons:
#IBAction func didTouchDownThreeButton(sender: AnyObject) {
threeButton.backgroundColor = blueColor
}
#IBAction func didTouchUpInsideThreeButton(sender: AnyObject) {
inputTextView.text = inputTextView.text + "3"
threeButton.backgroundColor = lightGrayColor
}
#IBAction func didTouchDragExitThreeButton(sender: AnyObject) {
threeButton.backgroundColor = lightGrayColor
}
#IBAction func didTouchDragEnterThreeButton(sender: AnyObject) {
threeButton.backgroundColor = blueColor
}
Any help would be much appreciated!
I managed to create a suitable solution by overriding the functions below and keeping track of which button was touched last and second to last. The last button touched gets highlighted and the second to last gets unhighlighted. Here's my code for a two button test in case anyone should find it useful:
#IBOutlet weak var bottomButton: UIButton!
#IBOutlet weak var topButton: UIButton!
var lastTouchedButton: UIButton? = nil
var secondToLastTouchedButton: UIButton? = nil
override func touchesBegan(touches: Set<UITouch>?, withEvent event: UIEvent?) {
let touch = touches?.first
let location : CGPoint = (touch?.locationInView(self.view))!
if topButton.pointInside(self.view.convertPoint(location, toView: topButton.viewForLastBaselineLayout), withEvent: nil) {
topButton?.backgroundColor = UIColor.redColor()
}
else if bottomButton.pointInside(self.view.convertPoint(location, toView: bottomButton.viewForLastBaselineLayout), withEvent: nil) {
bottomButton?.backgroundColor = UIColor.redColor()
}
super.touchesBegan(touches!, withEvent:event)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let location : CGPoint = (touch?.locationInView(self.view))!
if topButton.pointInside(self.view.convertPoint(location, toView: topButton.viewForLastBaselineLayout), withEvent: nil) {
secondToLastTouchedButton = lastTouchedButton
lastTouchedButton = topButton
lastTouchedButton?.backgroundColor = UIColor.redColor()
}
else if bottomButton.pointInside(self.view.convertPoint(location, toView: bottomButton.viewForLastBaselineLayout), withEvent: nil) {
secondToLastTouchedButton = lastTouchedButton
lastTouchedButton = bottomButton
lastTouchedButton?.backgroundColor = UIColor.redColor()
}
else {
lastTouchedButton?.backgroundColor = UIColor.whiteColor()
}
if secondToLastTouchedButton != lastTouchedButton {
secondToLastTouchedButton?.backgroundColor = UIColor.whiteColor()
}
super.touchesMoved(touches, withEvent: event)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let location : CGPoint = (touch?.locationInView(self.view))!
if topButton.pointInside(self.view.convertPoint(location, toView: topButton.viewForLastBaselineLayout), withEvent: nil) {
topButton?.backgroundColor = UIColor.whiteColor()
}
else if bottomButton.pointInside(self.view.convertPoint(location, toView: bottomButton.viewForLastBaselineLayout), withEvent: nil) {
bottomButton?.backgroundColor = UIColor.whiteColor()
}
super.touchesEnded(touches, withEvent: event)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
lastTouchedButton?.backgroundColor = UIColor.whiteColor()
super.touchesCancelled(touches, withEvent: event)
}
I've tried to do this by using touch drag inside/outside and touch drag enter/exit on the buttons, but it only works for the button where the touch originated
Absolutely right. The touch "belongs" to a view, and for as long as you keep dragging, it will still only belong to that view. Thus, the only way something like what you describe can be possible is for the touch detection to take place in the common superview of these button views.