Rotate UIImageView inside UIScrollView get X and Y inverted. Why? - ios

I have this code, which makes my image rotate, scale and pan to positioning it on screen.
The problem is, when I rotate the image more than 90º, the X and Y are inverted. i.e. if I move left, the image goes up and if I move to right, the image goes down. But when image is at 0º I can move it normally.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var imgLogo: UIImageView!
#IBOutlet weak var scrollLogo: UIScrollView!
var identity = CGAffineTransform.identity
override func viewDidLoad() {
super.viewDidLoad()
self.setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: false)
self.navigationItem.backBarButtonItem = UIBarButtonItem.init(title: " ", style: .plain, target: nil, action: nil)
self.navigationItem.backBarButtonItem?.tintColor = .white
}
func setupLayout(){
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(scale))
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(positioningImage))
pinchGesture.delegate = self
rotationGesture.delegate = self
panGesture.delegate = self
view.addGestureRecognizer(pinchGesture)
view.addGestureRecognizer(rotationGesture)
view.addGestureRecognizer(panGesture)
}
#objc func scale(_ gesture: UIPinchGestureRecognizer) {
switch gesture.state {
case .began:
identity = imgLogo.transform
case .changed,.ended:
imgLogo.transform = identity.scaledBy(x: gesture.scale, y: gesture.scale)
case .cancelled:
break
default:
break
}
}
#objc func rotate(_ gesture: UIRotationGestureRecognizer) {
imgLogo.transform = imgLogo.transform.rotated(by: gesture.rotation)
}
#objc func positioningImage(_ gesture: UIPanGestureRecognizer) {
let points = gesture.translation(in: scrollLogo)
imgLogo.transform = imgLogo.transform.translatedBy(x: points.x, y: points.y)
gesture.setTranslation(CGPoint.zero, in: scrollLogo)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Can someone help me indicate what is wrong? Where/how to fix?
Thanks

Your panning is currently relative to scrollLogo, if you change the points declaration to
let points = gesture.translation(in: imgLogo)
then the panning will be done relative to the image’s current translation, and it should work as intended!

Related

Swift -How to make a passthrough view recognize a down swipe gesture

I have a second window that has a view with a passthrough view inside of it. The passthrough works fine but I need it to recognize downSwipe gestures while still passing all other touch events to the view below it. How can I do this?
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
print("Passing all touches to the next view (if any), in the view stack.")
return false
}
}
class MyVC: UIViewController {
lazy var backdropView: PassThroughView = {
let v = PassThroughView(frame: self.view.bounds)
v.backgroundColor = .clear
v.isUserInteractionEnabled = true
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
addGesture()
}
func addGesture() {
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(handleGesture))
swipeDown.direction = .down
backdropView.addGestureRecognizer(swipeDown)
}
#objc func handleGesture(gesture: UISwipeGestureRecognizer) -> Void {
if gesture.direction == .down {
print("Swipe Down")
}
}
}

UIGesturerecognizer is not working on UIView

When I am tapping on UIView but Gesture is not working.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
I just want to perform this action on UIView tap.
You may check in the debug view hierarchy if anything with alpha = 0 is overlapping your viewForSHadow.
You have to implement as follows:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_sender:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
...
}
Your code is more than enough to have working UIGestureRecognizer, you should check some other stuff like, is there something else that can consume the user interaction. And also to check if you use
isUserInteractionEnabled = false
to some parent view of viewForSHadow.
You have to use UIGestureRecognizerDelegate
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tapRecognizer.delegate = self
blackView.addGestureRecognizer(tapRecognizer)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
#objc func handleDismiss() {
print("handleDismiss")
}
Reference
Don't forget to set the UIGestureRecognizerDelegate in your class
You need to mention numberOfTapsRequired property of the UITapGestureRecognizer.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_sender:)))
tap.numberOfTapsRequired = 1
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTapGesture(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
// Why are you hiding this view **viewForSHadow**
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
Also, make sure you are not doing viewForSHadow.isUserInteractionEnabled = false anywhere in the code.
First of all you code doesn't compile. The handleTap(_:) signature must be like:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
Secondly, you need to first try with the minimal code in a separate project. And by minimal code I mean what you've added in the question.
class ViewController: UIViewController {
#IBOutlet weak var viewForSHadow: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
}
Try with just the above code and see if you can get it working. The above code is working well at my end without any delegate or numberOfTaps.

UITapGestureRecognizer does not work with UIView animate

I'm trying to call an action when I tap on a UIImage at the time of its animation.
I've seen similar questions, but I could not apply these solutions to my case.
Please help.
xcode 9.2
swift 4
import UIKit
class MyCalssViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// action by tap
let gestureSwift2AndHigher = UITapGestureRecognizer(target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.addGestureRecognizer(gestureSwift2AndHigher)
}
// action by tap
#objc func actionUITapGestureRecognizer (){
print("actionUITapGestureRecognizer - works!") // !!! THIS DOES NOT WORK !!!
}
// hide UIImage before appear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
// show UIImage after appear with animation
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 10, animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
You just missed a line, enabling user interaction on the view.
override func viewDidLoad() {
super.viewDidLoad()
// action by tap
let gestureSwift2AndHigher = UITapGestureRecognizer(target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer(gestureSwift2AndHigher)
}
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.clickedImageView(_:)))
myImageview.isUserInteractionEnabled = true
myImageview.addGestureRecognizer(tapGesture)
// MARK: - Gesture Recognizer action
func clickedImageView(_ sender: UITapGestureRecognizer) {
// Write your action here.
}
You need to pass in the option .allowUserInteraction like this:
UIView.animate(withDuration: 10, delay: 0, options: .allowUserInteraction, animations: {
...
})

how to enable swipe gestures on imageview when it is embedded in scroll view

in this I had implemented the swipe gestures on image view and it is embedded in scroll view but gestures are not working here is my code any solution for this ?
collectionView.delegate = self
collectionView.dataSource = self
imageView.isUserInteractionEnabled = true
let swipeLeft = UISwipeGestureRecognizer(target: self, action:#selector(swiped(gesture:)))
swipeLeft.direction = .left
self.imageView.addGestureRecognizer(swipeLeft)
swipeLeft.cancelsTouchesInView = false
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(swiped(gesture:)))
swipeRight.direction = .right
self.imageView.addGestureRecognizer(swipeRight)
swipeRight.cancelsTouchesInView = false
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:)))
self.imageView.addGestureRecognizer(tap)
If you want swipe and scrolling both at work concurrently you have to implement gesture recognizer delegate.
// here are those protocol methods with Swift syntax
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return true
}
May be you could try this.Don't forget to delegate as self. :)
For your case, you want to have UI like Gallery View. Just Left and right swipe to change photo and tap/DoubleTap to zoom.
Don't need Swipe Gestures.
Just use Collection view with paging enabled. Each cell will display a single image. when Paging enabled, only a single cell will be shown at a time.
So just enable Paging enabled in collection view and try running.
If you want to zoom, when tapping a cell then, add TapGesture to CollectionView's parent UIScrollView and do write actions correspondingly.
For this case, I've used custom collection view cell.
I've just added my custom cell code for your reference as given below.
class ImageViewerCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func awakeFromNib() {
super.awakeFromNib()
self.scrollView.minimumZoomScale = 1.0
self.scrollView.maximumZoomScale = 6.0
self.scrollView.delegate = self
self.scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
self.scrollView.zoom(to: CGRect(origin: CGPoint.zero, size: scrollView.frame.size), animated: true)
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapAction(_:)))
doubleTap.numberOfTapsRequired = 2
self.scrollView.addGestureRecognizer(doubleTap)
}
func setURL(imageURL : URL, needLoader : Bool) {
DispatchQueue.main.async {
self.scrollView.setZoomScale(self.scrollView.minimumZoomScale, animated: true)
self.scrollView.zoom(to: CGRect(origin: CGPoint.zero, size: self.scrollView.frame.size), animated: true)
if needLoader {
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
self.imageView.pin_setImage(from: imageURL, completion: { (completed) in
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
})
}
else {
self.activityIndicator.isHidden = true
self.imageView.pin_setImage(from: imageURL, placeholderImage: placeHolderImage)
}
self.imageView.pin_updateWithProgress = true
}
}
#IBAction func doubleTapAction(_ sender: Any) {
if scrollView.zoomScale == scrollView.minimumZoomScale {
let touchPoint = (sender as! UITapGestureRecognizer).location(in: scrollView)
let scale = min(scrollView.zoomScale * 3, scrollView.maximumZoomScale)
let scrollSize = scrollView.frame.size
let size = CGSize(width: scrollSize.width / scale,
height: scrollSize.height / scale)
let origin = CGPoint(x: touchPoint.x - size.width / 2,
y: touchPoint.y - size.height / 2)
scrollView.zoom(to:CGRect(origin: origin, size: size), animated: true)
}
else {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
scrollView.zoom(to: CGRect(origin: CGPoint.zero, size: scrollView.frame.size), animated: true)
}
}
}
extension ImageViewerCollectionViewCell : UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
}
As given above, I've used a imageview inside UIScrollView. So when you double tapped that scroll view, scrollview will zoom in & out.
Hope you understand and hope it helps for sure.

Swift: how to detect if swipe occurred in subview?

Assume a UIView, ParentView, contains a subview, ChildView, and other subviews.
The UIViewController attaches a swipe gesture recognizer to ParentView.
Swipes on ChildView trigger this swipe handler.
Inside of ParentView's swipe handler, is there a way to detect if the swipe occurred on ChildView?
Per Josh's answer, here's the attempted code, which does not work:
class TestViewController: UIViewController {
#IBOutlet weak var targetView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
initSwipeGestures()
}
fileprivate func initSwipeGestures() {
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe))
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe))
let upSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe))
let downSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe))
leftSwipe.direction = .left
rightSwipe.direction = .right
upSwipe.direction = .up
downSwipe.direction = .down
view.addGestureRecognizer(leftSwipe)
view.addGestureRecognizer(rightSwipe)
view.addGestureRecognizer(upSwipe)
view.addGestureRecognizer(downSwipe)
}
func didViewSwipe(_ sender:UISwipeGestureRecognizer) {
let location = sender.location(in: view)
let touchedView = view.hitTest(location, with: nil)
// Ignore swipes if targetView was swiped
if touchedView == targetView {
print("YO YO YO")
}
}
}
You can use the UIView method hitTest(_ point: CGPoint, with event: UIEvent?) to get the subview under the gesture's location.
func swipe(_ recognizer: UISwipeGestureRecognizer)
{
let location = recognizer.location(in: self)
let touchedView = self.hitTest(location, with: nil)
}

Resources