How can I move image only in limited area? - ios

is there any way to limit the area of moving object?
More precisely i want that the white circle only moves around the green circle.[Screen Bellow] (The same like in the Clue app for tracking woman
Period cycle) Is there any way to do that? I was thinking about circle radius but I can not think of a way to determine the center of that circle.
import UIKit
class MainViewController: UIViewController {
//OUTLETS
#IBOutlet weak var movingCircle: UIImageView!
#IBAction func touchMovingCircle(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: self.view)
if let view = sender.view{
view.center = CGPoint(x:view.center.x + translation.x, y: view.center.y + translation.y)
}
let point = CGPoint(x: 0, y:0)
sender.setTranslation(point, in: self.view)
}
}

I would suggest trying to alter the white circle's anchor point such that it is in the center of the larger circle. Then you could simply rotate the circle and it would give the result you desire.
//Get the necessary y anchor point
let yAnchor = largeCircleRadius / movingCircleDiameter
movingCircle.layer = CGPoint(x: 0.5, y: yAnchor)
That may not be right, but it would be something like that. Then to move it around the circle, all you would have to do is set the rotation of the white circle.
movingCircle.transform = CGAffineTransform(rotationAngle: yourAngle)
You might have to mess with the values, but I've done something similar before.

Related

UIViewContainer transformation changes after UIButton title is changed

I have the storyboard in the below screenshot.
The gray area is a UIViewContainer. UIGestureRegonizers are enabling the zooming and the moving of the UIViewContainer through Pan-Pinch gestures. The location of the buttons are fixed in the bottom with constraints, the UIViewContainer has flexible size and connected to buttons and the safe are with constraints.
So here is the problem: First, I zoom into a part of the UIViewContainer and then, I change the title of the up-right button. Right after that, for some reason that I need help with, the container moves about 10 pixels to the right. I am sure that no lifecycle function is called after the button.setTitle() function. I think it can be the problem that the container does some kind of a relayout.
Here is a dummy app where I reproduced the same behavior. After you move the container to somewhere with a pan gesture and click the button, the button title changes and the view is moved back to center. I want the view to stay where it is.
How can I disable the layout after the button title is changed?
#IBAction func handlePinchGestures(pinchGestureRecognizer: UIPinchGestureRecognizer) {
if let view = pinchGestureRecognizer.view {
switch pinchGestureRecognizer.state {
case .changed:
let pinchCenter = CGPoint(x: pinchGestureRecognizer.location(in: view).x - view.bounds.midX,
y: pinchGestureRecognizer.location(in: view).y - view.bounds.midY)
let transform = view.transform.translatedBy(x: pinchCenter.x, y: pinchCenter.y)
.scaledBy(x: pinchGestureRecognizer.scale, y: pinchGestureRecognizer.scale)
.translatedBy(x: -pinchCenter.x, y: -pinchCenter.y)
view.transform = transform
pinchGestureRecognizer.scale = 1
imageLayerContainer.subviews.forEach({ (subview: UIView) -> Void in subview.setNeedsDisplay() })
default:
return
}
}
}
#IBAction func handlePanGesturesWithTwoFingers(panGestureRecognizer: UIPanGestureRecognizer) {
let translation = panGestureRecognizer.translation(in: self.view)
if let view = panGestureRecognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
panGestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}

Restrict pan gesture from moving outside of the left and right sides of the screen’s frame

I have an image view that pops up in the centre of the screen. You can pinch to zoom in or zoom out the image as well as move it horizontally. All these actions work perfectly. However I want to restrict the panning movements so users can't swipe beyond the left or right edges of the image. Below Ive posted screenshots along with explanations to show what I mean
Original View
Image moved to the right. At the moment you can see you can move it beyond the left edge of the image and it show black a black space. If the image width is equal to the screen width then I obviously want the pan gesture to be disable
Image zoomed in
Imaged moved to the left but again I want to be able to restrict the pan gesture to the edge of the image so there is no black space on the right of the image.
Ive tried looking around but I can't find anything that can help with my specific problem. Ive posted my code below. Any help would be much appreciated! Thanks in advance
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let zoomView = gestureRecognizer.view else{return}
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
if(gestureRecognizer.view!.center.x < 300) {
gestureRecognizer.view!.center = CGPoint(x:gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y)
}else{
gestureRecognizer.view!.center = CGPoint(x:299, y: gestureRecognizer.view!.center.y)
}
gestureRecognizer.setTranslation(CGPoint.zero, in: zoomView)
}
}
I followed this youtube video to get it done.
I can't help you with the zoom but I can help you stop the imageView from moving outside of the left and right sides when NOT zooming.
You didn't give any context as to wether or not your imageView was created in Storyboard or programmatically. The one in my example is programmatic and it's named orangeImageView.
Create a new project then just copy and paste this code inside of the View Controller. When you run the project you will see an orange imageView that you can move around but it won't go beyond the left or right side of the screen. I didn't bother with the top or bottom because it wasn't part of your question.
Follow Steps 1 - 8 inside #objc func panGestureHandler(_ gestureRecognizer: UIPanGestureRecognizer) for an explanation of what each step does and how it works.
Replace the orangeImageView with the imageView your using:
class ViewController: UIViewController {
// create frame in viewDidLoad
let orangeImageView: UIImageView = {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.backgroundColor = .orange
return imageView
}()
var panGesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
// create a frame for the orangeImageView and add it as a subview
orangeImageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
view.addSubview(orangeImageView)
// initialize the pangesture and add it to the orangeImageView
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureHandler(_:)))
orangeImageView.addGestureRecognizer(panGesture)
}
#objc func panGestureHandler(_ gestureRecognizer: UIPanGestureRecognizer){
// 1. use these values to restrict the left and right sides so the orangeImageView won't go beyond these points
let leftSideRestrction = self.view.frame.minX
let rightSideRestriction = self.view.frame.maxX
// 2. use these values to redraw the orangeImageView's correct size in either Step 6 or Step 8 below
let imageViewHeight = self.orangeImageView.frame.size.height
let imageViewWidth = self.orangeImageView.frame.size.width
if gestureRecognizer.state == .changed || gestureRecognizer.state == .began {
let translation: CGPoint = gestureRecognizer.translation(in: self.view)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
/*
3.
-get the the upper left hand corner of the imageView's X and Y origin to get the current location of the imageView as it's dragged across the screen.
-you need the orangeImageView.frame.origin.x value to make sure it doesn't go beyond the left or right edges
-you need the orangeImageView.frame.origin.y value to redraw it in Steps 6 and 8 at whatever Y position it's in when it hits either the left or right sides
*/
let imageViewCurrentOrginXValue = self.orangeImageView.frame.origin.x
let imageViewCurrentOrginYValue = self.orangeImageView.frame.origin.y
// 4. get the right side of the orangeImageView. It's computed using the orangeImageView.frame.origin.x + orangeImageView.frame.size.width
let imageViewRightEdgePosition = imageViewCurrentOrginXValue + imageViewWidth
// 5. if the the orangeImageView.frame.origin.x touches the left edge of the screen or beyond it proceed to Step 6
if imageViewCurrentOrginXValue <= leftSideRestrction {
// 6. redraw the orangeImageView's frame with x: being the far left side of the screen and Y being where ever the current orangeImageView.frame.origin.y is currently positioned at
orangeImageView.frame = CGRect(x: leftSideRestrction, y: imageViewCurrentOrginYValue, width: imageViewWidth, height: imageViewHeight)
}
// 7. if the the orangeImageView.frame.origin.x touches the right edge of the screen or beyond it proceed to Step 8
if imageViewRightEdgePosition >= rightSideRestriction{
// 8. redraw the orangeImageView's frame with x: being the rightSide of the screen - the orangeImageView's width and y: being where ever the current orangeImageView.frame.origin.y is currently positioned at
orangeImageView.frame = CGRect(x: rightSideRestriction - imageViewWidth, y: imageViewCurrentOrginYValue, width: imageViewWidth, height: imageViewHeight)
}
}
}
}

Scale and move an UIView using UIPanGestureRecognizer

I have a UIView A. I put a icon on view A and try to use pan gesture to scale and move this view A.
I have try many solution, but i can not make it.
Please suggest help me?
More detail:
I will add more detail.
I have view A, add sub on self view. And i want when i draw panGestureRegonizer on view A, view A will move follow draw.
And while moving view A will scale. View A will scale to smaller when view move to top/left/bottom/right of sreen and scale to larger when view move to center of screen.
Let's say you have vc - ViewController and your UIView A is a subview of vc. You can add UIPanGestureRecognizer to A. Then drag the panGestureRegonizer to your vc as an action:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
//your code here
}
From the sender you can check view , location and state of the action. The state might impact your code in some cases, depending on what you are trying to achieve.
Then you need to modify the action to this:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
UIView.animateKeyframes(withDuration: 0.1, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear, animations: {
let location = sender.location(in: sender.view?.superview)
sender.view?.center = location
})
}
Here sender.view?.superview equals vc.view. This code snippet will detect the pan gesture, and then will move A so A.center is matching the gesture's location. Note that duration 0.1 is giving smooth animation effect to the movement.
This will give you "move" functionality with pan gesture.
EDIT for scaling:
Logic: You have coordinate system(CS) with center, x and y. When the user uses pan gesture, he/she generates sequence of points in the CS. So our task is to measure the distance between the center of the CS and users' points. When we have the furthest distance, we can calculate scale factor for our scaling view.
var center: CGPoint! //center of the CS
let maxSize: CGSize = CGSize.init(width: 100, height: 100) // maximum size of our scaling view
var maxLengthToCenter: CGFloat! //maximum distance from the center of the CS to the furthest point in the CS
private func prepareForScaling() {
self.center = self.view.center //we set the center of our CS to equal the center of the VC's view
let frame = self.view.frame
//the furthest distance in the CS is the diagonal, and we calculate it using pythagoras theorem
self.maxLengthToCenter = (frame.width*frame.width + frame.height*frame.height).squareRoot()
}
Then we need to call our setup functional to have our data ready for scaling functionality - we can do this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.prepareForScaling()
}
Then we need a helper function to calculates the scaled size of our view, for user's pan gesture current position on the screen.
private func scaledSize(for location: CGPoint) -> CGSize {
//calculate location x,y differences from the center
let xDifference = location.x - self.center.x
let yDifference = location.y - self.center.y
//calculate the scale factor - note that this factor will be between 0.0(center) and 0.5(diagonal - furthest point)
//It is due our measurement - from center to view's edge. Consider multiplying this factor with your custom constant.
let scaleFactor = (xDifference*xDifference + yDifference*yDifference).squareRoot()/maxLengthToCenter
//create scaled size with maxSize and current scale factor
let scaledSize = CGSize.init(width: maxSize.width*(1-scaleFactor), height: maxSize.height*(1-scaleFactor))
return scaledSize
}
And finally, we need to modify our pan gesture action to change the size of A:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
UIView.animateKeyframes(withDuration: 0.1, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear, animations: {
let location = sender.location(in: sender.view?.superview)
sender.view?.frame = CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: self.scaledSize(for: location))
sender.view?.center = location
})
}

Dragging a UILabel within a UIView and maintaining it's new coordinates

So I have a UILabel on a UIView nib file and I am trying to give the user the ability to drag the label around and when they stop dragging the label stays there and if they try to drag it again they can.
I am able to drag the Label just fine using the UIPanGestureRecognizer and when I stop dragging it the label stays where it is. The problem I am having is that when I attempt to drag it again the Label jumps back to the original starting position at the center of the UIView.
I understand that using the UIPanGestureRecognizer.translation(in: UIVIew) gives the original coordinates as (x: 0.0, y: 0.0) when the actual coordinates that the Label starts at are (x: 150.0, y: 90.0).
#IBOutlet var cardView: UIView!
#IBOutlet weak var testLBL: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// trying to move the label
let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.wasDragged(gestureRecognizer:)))
testLBL.isUserInteractionEnabled = true
cardView.clipsToBounds = true
testLBL.addGestureRecognizer(gesture)
}
Below is the function that is used to handle the action, I have several other things I have tried commented out. I am also printing the current coordinates of the Label within the cardView
// function that helps drag the label around
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: cardView)
//print(translation)
// currently taking it from the original point each time you try to drag it
testLBL.center = CGPoint(x: self.bounds.width / 2 + translation.x, y: self.bounds.height / 2 + translation.y)
//how you will know what position the label was moved to
print(["x",self.testLBL.frame.origin.x,"y", self.testLBL.frame.origin.y])
//let newTranslation = gestureRecognizer.translation(in: cardView)
//var coordinates = CGPoint(x: self.testLBL.frame.origin.x, y: self.testLBL.frame.origin.y)
}
The testLBL.center = CGPoint(x: self.bounds.width / 2 + translation.x, y: self.bounds.height / 2 + translation.y) operation is what allows the label to move and works find I just want it to not jump back to the middle of the UIView when I try to drag it again.
Any help is greatly appreciated because I am still pretty new at coding, Thank You!
// function that helps drag the label around
#objc
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: self.view)
let selectedLabel = gestureRecognizer.view!
selectedLabel.center = CGPoint(x: selectedLabel.center.x + translation.x,
y: selectedLabel.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
print(["1 x",self.testLbl.frame.origin.x,"y", self.testLbl.frame.origin.y])
}
Tested this code and it works, hope it helps anyone!
this might help
CGPoint translation = [recognizer translationInView:self.superview];
testLBL.center = CGPointMake(self.center.x + translation.x, self.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self];
apply it on swift you won't find that hard

How to constraint a view/layer's move path to a Bezier curve path?

Before asking the question, i have searched the SO:
iPhone-Move UI Image view along a Bezier curve path
But it did not give a explicit answer.
My requirement is like this, I have a bezier path, and a view(or layer if OK), I want to add pan gesture to the view, and the view(or layer)'s move path must constraint to the bezier path.
My code is below:
The MainDrawView.swift:
import UIKit
class MainDrawView: UIView {
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
drawArc()
}
func drawArc() {
let context = UIGraphicsGetCurrentContext()
// set start point
context?.move(to: CGPoint.init(x: 0, y: 400))
//draw curve
context?.addQuadCurve(to: CGPoint.init(x: 500, y: 250), control: CGPoint.init(x: UIScreen.main.bounds.size.width, y: 200))
context?.strokePath()
}
}
The ViewController.swift:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var clickButton: UIButton!
lazy var view1:UIView = {
let view: UIView = UIView.init(frame: CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: CGSize.init(width: 10, height: 10)))
view.center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: UIScreen.main.bounds.size.height / 2.0)
view.backgroundColor = UIColor.red
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
initData()
initUI()
}
func initData() {
}
func initUI() {
self.clickButton.isHidden = true
// init the view
self.view.addSubview(self.view1)
}
}
Edit -1
My deep requirment is when I use pan guester move the view/layer, the view will move on the bezier path.
If bezier path is off lines then u can find the slope of the line and for every change in x or y you can calculate the position of the bezier path
var y :Float = (slope * (xPos-previousCoord.x))+previousCoord.y; xPos is continuously changing. Similarly, u can find for x. For any closed shape with line segments, you can use this.
But if u need it for circle, then u need to convert cartesian to polar. i.e.., from coordinates u can find the angle, then from that angle, you have the radius so by using that angle u need to find cartesian coordinates from that.
θ = tan-1 ( 5 / 12 )
U need to use mainly 3 coordinates one is centre of circle, the second one is your touch point, and the last one is (touchpoint.x, centreofcircle.y). from centre of circle calculate distance between two coordinates
You have θ and radius of circle then find points using
x = r × cos( θ )
y = r × sin( θ )
Don't mistake r in the image for the radius of the circle, r in the image is the hypotenuse of three coordinates. You should calculate for every change of x in touch point.
Hope it works. But for irregular shapes I am not sure how to find.
It sounds like you'll probably have to do some calculations. When you set your method to handle the UIPanGestureRecognizer, you can get a vector back for the pan, something like this:
func panGestureRecognized(_ sender: UIPanGestureRecognizer) {
let vector:CGPoint = sender.translation(in: self.view) // Or use whatever view the UIPanGestureRecognizer was added to
}
You could then use that to extrapolate the movement along the x and y axis. I would recommend starting by getting an algebraic equation for the path you're moving the view on. To move the view along that line, you're going to have to be able to calculate a point along that line relative to the movement of the UIPanGestureRecognizer, and then update the position of the view using that calculated point along the line.
I don't know if it'll work for what you're trying to do, but if you want to try animating your view along the path, it's actually pretty easy:
let path = UIBezierPath() // This would be your custom path
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false animation.repeatCount = 0
animation.duration = 5.0 // However long you want
animation.speed = 2 // However fast you want
animation.calculationMode = kCAAnimationPaced
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animatingView.layer.add(animation, forKey: "moveAlongPath")

Resources