I am working on a photo editor app. In this app an effect is something like a Ring shape.
I have two image views and I want to pinch zoom both image views in a way that shows like only one image view zooming. And the ring is completing by two image views.
So main issue is that the ring is behaving like half on one image view and half on other i want to attach them.
#objc func handlePan6(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: shapeImageView4)
let translation2 = gestureRecognizer.translation(in: shapeImageView2)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation2.x, y: gestureRecognizer.view!.center.y + translation2.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: shapeImageView4)
gestureRecognizer.setTranslation(CGPoint.zero, in: shapeImageView2)
}
}
#objc func pinchRecognized(pinch: UIPinchGestureRecognizer) {
if let view = pinch.view {
view.transform = view.transform.scaledBy(x: pinch.scale, y: pinch.scale)
pinch.scale = 1
}
}
#objc func pinchRecognized2(pinch: UIPinchGestureRecognizer) {
if let view = pinch.view {
neonImageView2.transform = view.transform.scaledBy(x: pinch.scale, y: pinch.scale)
pinch.scale = 1
}
}
Related
I need your help guys. I have game scene and func which allow to move camera using panGesture. Also i need pinchGesture to zoom in and out my SKScene. I found some code here, but it lags. Can plz someone help me to improve this code?
`
#objc private func didPinch(_ sender: UIPinchGestureRecognizer) {
guard let camera = self.camera else {return}
if sender.state == .changed {
previousCameraScale = camera.xScale
}
camera.setScale(previousCameraScale * 1 / sender.scale)
sender.scale = 1.0
}
`
try this pinch code.
//pinch -- simple version
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//reset value for next time
recognizer.scale = 1.0
}
}
although i would recommend this slightly more complicated version which centers the pinch around the touch point. makes for a much nicer pinch in my experience.
//pinch around touch point
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
//cache location prior to scaling
let locationInView = recognizer.location(in: self.view)
let location = self.convertPoint(fromView: locationInView)
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//zoom around touch point rather than center screen
let locationAfterScale = self.convertPoint(fromView: locationInView)
let locationDelta = location - locationAfterScale
let newPoint = camera.position + locationDelta
camera.position = newPoint
//reset value for next time
recognizer.scale = 1.0
}
}
//also need these extensions to add and subtract CGPoints
extension CGPoint {
static func + (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x + b.x, y: a.y + b.y)
}
static func - (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x - b.x, y: a.y - b.y)
}
}
I have a rotateArt function that rotates a UIImage - my problem is when this function is called following a pan gesture the image snaps back to the views centre. I would like it to rotate around its current centre.
I have tried assigning a variable to hold its current centre but that did not work. What am I missing?
Thanks
extension MainViewController {
func imageCheck() {
pickedImage != nil ? addPainting() : print("image did not load")
}
func addPainting() {
pickedImageView = UIImageView(image: pickedImage)
pickedImageView.isUserInteractionEnabled = true
pickedImageView.contentMode = .scaleAspectFit
view.addSubview(pickedImageView)
pickedImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pickedImageView.centerXAnchor.constraint(equalTo: userPickedRoom.centerXAnchor),
pickedImageView.centerYAnchor.constraint(equalTo: userPickedRoom.centerYAnchor, constant: -50),
pickedImageView.widthAnchor.constraint(equalToConstant: 150),
pickedImageView.heightAnchor.constraint(equalToConstant: 150)
])
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
pickedImageView.addGestureRecognizer(panGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch))
view.addGestureRecognizer(pinchGesture)
addShadow()
}
#objc func handlePan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
guard let gestureView = sender.view else {return}
gestureView.center = CGPoint(x: gestureView.center.x + translation.x, y: gestureView.center.y + translation.y)
sender.setTranslation(.zero, in: view)
}
#objc func handlePinch(_ sender: UIPinchGestureRecognizer) {
guard sender.view != nil else { return }
if sender.state == .began || sender.state == .changed {
pickedImageView.transform = (pickedImageView.transform.scaledBy(x: sender.scale, y: sender.scale))
sender.scale = 1.0
}
}
func rotateArt() { // pickedImageView.center returns to view.center when this functinon is called.
UIImageView.animate(withDuration: 0.1, animations: {
self.pickedImageView.transform = self.pickedImageView.transform.rotated(by: .pi / 2)
})
}
func addShadow() {
pickedImageView.layer.shadowColor = UIColor.black.cgColor
pickedImageView.layer.shadowOpacity = 1
pickedImageView.layer.shadowOffset = CGSize(width: 0.1, height: 0.5)
pickedImageView.layer.shadowRadius = 1
pickedImageView.layer.shouldRasterize = true
pickedImageView.layer.rasterizationScale = UIScreen.main.scale
}
}
The problem is these lines:
pickedImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate
You cannot position pickedImageView using constraints and also animate it by changing its center (as you do during the pan gesture).
Constraints are one thing. Frames/centers are a different thing. They are opposites. You have created a conflict here: you say one thing by changing the center during the pan, but the constraints say something else (they still want to put the image view where you told them to put it).
You need to resolve that conflict. The easiest way is not to use constraints at all, but there are other ways.
I currently have the code to move the UIImage to wherever I tap on the screen, however my app requires the user to be able to drag the image about on the screen and my current code doesn't do that. I am new to the language so any help would be appreciated. Here is my current code for getting the location of the touch:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?{ if let touch = touches.first { let location = touch.location(in: self.view)
You could do it yourself by implementing both touchesBegan() and touchesMoved(), but you'd be better off using a UIPanGestureRecognzier. I'd suggest finding a sample project that lets you drag views using UIPanGestureRecognzier. It will save you some head-scratching.
I had made cube which is basically a UIView that can be dragged around and the info changes inside the cube. I used the following function to drag the cube in the view. See if it helps
var draggableCube = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
#objc func panGestureDetected(panGestureRecognizer: UIPanGestureRecognizer) {
let translation = panGestureRecognizer.translation(in: self.view)
var changeX : CGFloat = 0
var changeY : CGFloat = 0
if translation.x + self.draggableCube.frame.maxX > self.view.bounds.maxX {
// prevents it to go outside of the bounds from right side
changeX = self.view.bounds.maxX - self.draggableCube.frame.maxX
} else if translation.x + self.draggableCube.frame.minX < self.view.bounds.minX{
// prevents it to go outside of the bounds from right side
changeX = self.view.bounds.minX - self.draggableCube.frame.minX
} else {
// translation is within limits
changeX = translation.x
}
if translation.y + self.draggableCube.frame.maxY > self.view.bounds.maxY {
// prevents it to go outside of the bounds from bottom
changeY = self.view.bounds.maxY - self.draggableCube.frame.maxY
} else if translation.y + self.draggableCube.frame.minY < self.view.bounds.minY {
// prevents it to go outside of the bounds from top
changeY = self.view.bounds.minY - self.draggableCube.frame.minY
} else {
// translation is within limits
changeY = translation.y
}
self.draggableCube.center = CGPoint(x: self.draggableCube.center.x + changeX, y: self.draggableCube.center.y + changeY)
panGestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
if panGestureRecognizer.state == .ended {
// implement action what you want to do after the gragging ended
}
}
Let's say I have an animator that moves a view from (0, 0) to (-120, 0):
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.8)
animator.addAnimations {
switch state:
case .normal: view.frame.origin.x = 0
case .swiped: view.frame.origin.x = -120
}
}
I use it together with UIPanGestureRecognizer, so that I can resize the view continuously along with the finger movements.
The issue comes when I want to add some sort of bouncing effect at the start or at the end of the animation. NOT just the damping ratio, but the bounce effect. The easiest way to imagine this is Swipe-To-Delete feature of UITableViewCell, where you can drag "Delete" button beyond its actual width, and then it bounces back.
Effectively what I want to achieve, is the way to set fractionComplete property outside of [0, 1] segment, so when the fraction is 1.2, the offset becomes 144 instead of its 120 maximum.
And right now the maximum value for fractionComplete is exactly 1.
Below are some examples to have this issue visualized:
What I currently have:
What I want to achieve:
EDIT (19 January):
Sorry for my delayed reply. Here are some clarifications:
I don't use UIView.animate(...), and use UIViewPropertyAnimator instead for a very specific reason: it handles for me all the timings, curves and velocities.
For example, you dragged the view halfway through. This means that duration of the remaining part should be two times less than total duration. Or if you dragged though the 99% of the distance, it should complete the remaining part almost instantly.
As an addition, UIViewPropertyAnimator has such features as pause (when user starts dragging once again), or reverse (when user started dragging to the left, but after that he changed his mind and moved the finger to the right), that I also benefit from.
All this is not available for simple UIView animations, or requires TONS of effort at best. It is only capable of simple transitions, and this is not the case.
That's why I have to use some sort of animator.
And as I mentioned in the comments thread in the answer that was removed by its publisher, the most complex part for me here is to simulate the friction effect: the further you drag, the less the view actually moves. Just as when you're trying to drag any UIScrollView outside of it's content.
Thanks for your effort guys, but I don't think any of these 2 answers is relevant. I will try to implement this behaviour using UIDynamicAnimator whenever I have time. Probably in the nearest week or two. I will publish my approach in case I have any decent results.
EDIT (20 January):
I just uploaded a demo project to the GitHub, which includes all the transitions that I have in my project. So now you can actually have an idea why do I need to use animators and how I use them: https://github.com/demon9733/bouncingview-prototype
The only file you are actually interested in is MainViewController+Modes.swift. Everything related to transitions and animations is contained there.
What I need to do is to enable user to drag the handle area beyond "Hide" button width with a damping effect. "Hide" button will appear on swiping the handle area to the left.
P.S. I didn't really test this demo, so it can have bugs that I don't have in my main project. So you can safely ignore them.
you need to allow pan gesture to get to needed x position and at the end of pan an animation is needed to be triggered
one way to do this would be:
var initial = CGRect.zero
override func viewDidLayoutSubviews() {
initial = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initial
let open = initial.offsetBy(dx: -120, dy: 0)
// 1 manage panning along x direction
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 2 animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = closed
})
} else {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = open
})
}
}
}
Edit 20 Jan
For simulating dampening effect and make use of UIViewPropertyAnimator specifically,
var initialOrigin = CGRect.zero
override func viewDidLayoutSubviews() {
initialOrigin = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initialOrigin
let open = initialOrigin.offsetBy(dx: -120, dy: 0)
// 1. to simulate dampening
var multiplier: CGFloat = 1.0
if animatedView?.frame.origin.x ?? CGFloat(0) > closed.origin.x || animatedView?.frame.origin.x ?? CGFloat(0) < open.origin.x {
multiplier = 0.2
} else {
multiplier = 1
}
// 2. animate panning
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x * multiplier, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 3. animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = closed.origin.x
})
animate.startAnimation()
} else {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = open.origin.x
})
animate.startAnimation()
}
}
}
Here is possible approach (simplified & a bit scratchy - only bounce, w/o button at right, because it would much more code and actually only a matter of frames management)
Due to long delay of UIPanGestureRecognizer at ending, I prefer to use UILongPressGestureRecognizer, as it gives faster feedback.
Here is demo result
The Storyboard of used below ViewController has only gray-background-rect-container view, everything else is done in code provided below.
class ViewController: UIViewController {
#IBOutlet weak var container: UIView!
let imageView = UIImageView()
var initial: CGFloat = .zero
var dropped = false
private func excedesLimit() -> Bool {
// < set here desired bounce limits
return imageView.frame.minX < -180 || imageView.frame.minX > 80
}
#IBAction func pressHandler(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: imageView.superview).x
if sender.state == .began {
dropped = false
initial = location - imageView.center.x
}
else if !dropped {
if (sender.state == .changed) {
imageView.center = CGPoint(x: location - initial, y: imageView.center.y)
dropped = excedesLimit()
}
if sender.state == .ended || dropped {
initial = .zero
// variant with animator
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut) {
let stickTo: CGFloat = self.imageView.frame.minX < -100 ? -100 : 0 // place for button at right
self.imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: self.imageView.frame.origin.y), size: self.imageView.frame.size)
}
animator.isInterruptible = true
animator.startAnimation()
// uncomment below - variant with UIViewAnimation
// UIView.beginAnimations("bounce", context: nil)
// UIView.setAnimationDuration(0.2)
// UIView.setAnimationTransition(.none, for: imageView, cache: true)
// UIView.setAnimationBeginsFromCurrentState(true)
//
// let stickTo: CGFloat = imageView.frame.minX < -100 ? -100 : 0 // place for button at right
// imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: imageView.frame.origin.y), size: imageView.frame.size)
// UIView.setAnimationDelegate(self)
// UIView.setAnimationDidStop(#selector(makeBounce))
// UIView.commitAnimations()
}
}
}
// #objc func makeBounce() {
// let bounceAnimation = CABasicAnimation(keyPath: "position.x")
// bounceAnimation.duration = 0.1
// bounceAnimation.repeatCount = 0
// bounceAnimation.autoreverses = true
// bounceAnimation.fillMode = kCAFillModeBackwards
// bounceAnimation.isRemovedOnCompletion = true
// bounceAnimation.isAdditive = false
// bounceAnimation.timingFunction = CAMediaTimingFunction(name: "easeOut")
// imageView.layer.add(bounceAnimation, forKey:"bounceAnimation");
// }
override func viewDidLoad() {
super.viewDidLoad()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "cat")
imageView.contentMode = .scaleAspectFill
imageView.layer.borderColor = UIColor.red.cgColor
imageView.layer.borderWidth = 1.0
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
container.addSubview(imageView)
imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1).isActive = true
imageView.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1).isActive = true
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(pressHandler(_:)))
pressGesture.minimumPressDuration = 0
pressGesture.allowableMovement = .infinity
imageView.addGestureRecognizer(pressGesture)
}
}
I am trying to move one image on base image.
If I am moving that UIView, it crosses UIImageView's image.
How to restrict UIView's move within UIImageView's image? It should supports for square and rectangle. Moving view should not cross its border.
Kindly guide me.
In the screen shots, UIImageView background color is black color.
Moving UIView is in white color. White color view is crossing its image [Screen Shot 2]. How to restrict within the image ?
Code:
#IBAction func panAcn(sender: UIPanGestureRecognizer) {
if sender.state == .Began || sender.state == .Changed {
let translation = sender.translationInView(self.myImgVw)
// note: 'view' is optional and need to be unwrapped
sender.view!.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointMake(0,0), inView: self.myImgVw)
}
}
Need Output:
My Output
You may try this :
#IBAction func panAcn(sender: UIPanGestureRecognizer) {
if sender.state == .Began || sender.state == .Changed {
let translation = sender.translationInView(self.myImgVw)
// note: 'view' is optional and need to be unwrapped
guard let view = sender.view else { return }
var newFrame = view.frame
newFrame.origin.x = max(self.myImgVw.frame.origin.x, view.frame.origin.x + translation.y)
newFrame.origin.y = max(self.myImgVw.frame.origin.y, view.frame.origin.y + translation.y)
view.frame = newFrame
}
}