问题
I have created 5 UIViews, each with UIPanGestureRecognizers, which I then add to another UIView. All this works and I can drag each around, but I don't want them to be dragged outside the boundaries of their parent UIView. I have read that I can implement UIGestureRecognizerDelegate with the gestureRecognizerShouldBegin function to achieve this, but I can't find enough documentation on exactly how to achieve exactly what I need. Apple's documentation on this, as usual, is quite vague. My code is:
let foreheadTopViewWidth = foreheadTopView.frame.width
let foreheadTopViewHeight = foreheadTopView.frame.height
let touchPointOffset = touchPointSize / 2
let touchPointYPos = (foreheadTopViewHeight / 2) - touchPointOffset
let touchPointForehead1 = TouchPoint(touchPointSize: touchPointSize, xPosition: 0, yPosition: touchPointYPos, treatmentArea: .upperForehead)
let touchPointForehead2 = TouchPoint(touchPointSize: touchPointSize, xPosition: ((foreheadTopViewWidth * 0.50) - touchPointOffset) / 2, yPosition: touchPointYPos, treatmentArea: .upperForehead)
let touchPointForehead3 = TouchPoint(touchPointSize: touchPointSize, xPosition: (foreheadTopViewWidth * 0.50) - touchPointOffset, yPosition: touchPointYPos, treatmentArea: .upperForehead)
let touchPointForehead4 = TouchPoint(touchPointSize: touchPointSize, xPosition: (foreheadTopViewWidth * 0.75) - (touchPointOffset / 0.75), yPosition: touchPointYPos, treatmentArea: .upperForehead)
let touchPointForehead5 = TouchPoint(touchPointSize: touchPointSize, xPosition: foreheadTopViewWidth - touchPointSize, yPosition: touchPointYPos, treatmentArea: .upperForehead)
foreheadTopView.addSubview(touchPointForehead1)
foreheadTopView.addSubview(touchPointForehead2)
foreheadTopView.addSubview(touchPointForehead3)
foreheadTopView.addSubview(touchPointForehead4)
foreheadTopView.addSubview(touchPointForehead5)
let foreheadGesture1 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
let foreheadGesture2 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
let foreheadGesture3 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
let foreheadGesture4 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
let foreheadGesture5 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
foreheadGesture1.delegate = self
foreheadGesture2.delegate = self
foreheadGesture3.delegate = self
foreheadGesture4.delegate = self
foreheadGesture5.delegate = self
touchPointForehead1.addGestureRecognizer(foreheadGesture1)
touchPointForehead2.addGestureRecognizer(foreheadGesture2)
touchPointForehead3.addGestureRecognizer(foreheadGesture3)
touchPointForehead4.addGestureRecognizer(foreheadGesture4)
touchPointForehead5.addGestureRecognizer(foreheadGesture5)
foreheadTopView.layer.addSublayer(touchPointForehead1.lineTo(touchpoint: touchPointForehead2))
foreheadTopView.layer.addSublayer(touchPointForehead2.lineTo(touchpoint: touchPointForehead3))
foreheadTopView.layer.addSublayer(touchPointForehead3.lineTo(touchpoint: touchPointForehead4))
foreheadTopView.layer.addSublayer(touchPointForehead4.lineTo(touchpoint: touchPointForehead5))
@objc func didPan(gesture: UIPanGestureRecognizer) {
guard let touchpoint = gesture.view as? TouchPoint else {
return
}
if (gesture.state == .began) {
touchpoint.center = gesture.location(in: foreheadTopView)
}
let newCenter: CGPoint = gesture.location(in: foreheadTopView)
let dX = newCenter.x - touchpoint.center.x
let dY = newCenter.y - touchpoint.center.y
touchpoint.center = CGPoint(x: touchpoint.center.x + dX, y: touchpoint.center.y + dY)
if let outGoingTouchPoint = touchpoint.outGoingTouchPoint, let line = touchpoint.outGoingLine, let path = touchpoint.outGoingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: touchpoint.center)
newPath.addLine(to: outGoingTouchPoint.center)
line.path = newPath.cgPath
}
if let inComingTouchPoint = touchpoint.inComingTouchPoint, let line = touchpoint.inComingLine, let path = touchpoint.inComingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: inComingTouchPoint.center)
newPath.addLine(to: touchpoint.center)
line.path = newPath.cgPath
}
}
The above code works fine in terms of what I need it to do in terms of panning/dragging. I have also extended my ViewController to conform to UIGestureRecognizerDelegate
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
}
}
But I am unsure where to go from here in how to restrict the draggable views within the boundaries of their parent view. Can anybody help?
回答1:
No, delegate is not appropriate for this thing. I believe you want to keep gestures and just restrict the position of your views to be inside their parent. To do so you need to restrict that on pan gesture change event.
The easiest is to check if two opposite points are within parent rect:
let topLeft = CGPoint(x: touchpoint.frame.minX + dX, y: touchpoint.frame.minY + dY)
let bottomRight = CGPoint(x: touchpoint.frame.maxX + dX, y: touchpoint.frame.maxY + dY)
if let containerView = touchpoint.superview, containerView.bounds.contains(topLeft), containerView.bounds.contains(bottomRight) {
touchpoint.center = CGPoint(x: touchpoint.center.x + dX, y: touchpoint.center.y + dY)
}
So basically we are computing the new top-left and bottom-right points. And if these are inside their superview bounds then view may be moved to that point.
This is pretty good but the thing is that once this occurs your view may not be exactly at the border. If user for instance drags it toward left really quickly then at some point topLeft
may be (15, 0)
and in the next event (-10, 0)
which means it will ignore the second one and keep at (15, 0)
.
So you need to implement the limi to movement and restrict it to it...
var targetFrame = CGRect(x: touchpoint.frame.origin.x + dX, y: touchpoint.frame.origin.y + dY, width: touchpoint.frame.width, height: touchpoint.frame.height)
if let containerView = touchpoint.superview {
targetFrame.origin.x = max(0.0, targetFrame.origin.x)
targetFrame.origin.y = max(0.0, targetFrame.origin.y)
targetFrame.size.width = min(targetFrame.size.width, containerView.bounds.width-(targetFrame.origin.x+targetFrame.width))
targetFrame.size.height = min(targetFrame.size.height, containerView.bounds.height-(targetFrame.origin.y+targetFrame.height))
}
touchpoint.frame = targetFrame
So this should make your view stick inside the superview...
回答2:
You have to do some calculations, I hope this code helps you.
@objc func handlepan(_ gesture:UIPanGestureRecognizer)
{
let translation = gesture.translation(in: self.imagelayer)
if let view1 = gesture.view {
if (view1.frame.origin.x + translation.x >= 0) && (view1.frame.origin.y + translation.y >= 0) && (view1.frame.origin.x + translation.x <= view1.frame.width ) && (view1.frame.origin.y + translation.y <= view1.frame.height)
{
view1.center = CGPoint(x:view1.center.x + translation.x,
y:view1.center.y + translation.y)
}
}
gesture.setTranslation(CGPoint.zero, in: self.imagelayer)
}
来源:https://stackoverflow.com/questions/49008643/setting-boundaries-on-pan-gestures