I have a tap gesture recognizer hooked up to my image view. When the image is tapped it becomes full screen and when tapped again it is dismissed. The user has the ability to pinch to zoom the image, however when the image is held down and moved around by the user it shows the background view. I want to hide the background so the view can not be seen until the image is dismissed. I think the images I have provided will explain better than I can through words.
var newImageView: UIImageView!
#IBAction func imageTapped(_ sender: UITapGestureRecognizer) {
self.navigationController?.setNavigationBarHidden(true, animated: true)
let imageView = sender.view as! UIImageView
let scrollView = UIScrollView(frame: self.view.frame)
newImageView = UIImageView(image: imageView.image)
newImageView.frame = self.view.frame
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
scrollView.addGestureRecognizer(tap)
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 3.0
scrollView.addSubview(newImageView)
self.view.addSubview(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView?
{
return newImageView;
}
func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
self.navigationController?.setNavigationBarHidden(false, animated: true)
sender.view?.removeFromSuperview()
}
You can simply hide an element with the backgroundElement.isHidden = true property. You can reset the background using backgroundElement.isHidden = false once the view has been dismissed.
Use following method of UIScrollView to check at what Scale it is zoomed.
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
print(scale)
if scale == 1.0 {
self.lblDesc.isHidden = false
self.btnDelete.isHidden = false
} else {
}
}
When scale is 1.0 it is normal minimumZoomScale.
Following is method called when you zoom. Use it to hide your elements.
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.lblDesc.isHidden = true
self.btnDelete.isHidden = true
}
Hope it helps you.
Related
I have a subview with a textfield. I'd like the keyboard to be dismissed when I tap anywhere outside of the textfield, but the problem is that taps within the subview aren't being registered and only taps outside of the subview dismiss the keyboard.
My view has a subview (coverView):
var coverView: UIView = {
let cover = UIView()
cover.translatesAutoresizingMaskIntoConstraints = false
cover.backgroundColor = .white
cover.widthAnchor.constraint(equalToConstant: 300).isActive = true
cover.heightAnchor.constraint(equalToConstant: 300).isActive = true
cover.clipsToBounds = true
return cover
}()
This coverView has its own ImageView:
var coverImageView: UIImageView = {
let imageView = UIImageView(image: nil)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .white
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = true
return imageView
}()
The coverView has a UITextfield added to it when tapped:
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
guard self.textfield1.text == "" else {return}
self.coverView.addSubview(textfield1)
self.textfield1.centerXAnchor.constraint(equalTo: self.coverView.centerXAnchor).isActive = true
self.textfield1.centerYAnchor.constraint(equalTo: self.coverView.centerYAnchor).isActive = true
self.textfield1.becomeFirstResponder()
}
If the user taps anywhere I'd like the keyboard to dismiss. I do this using the following code:
#objc func keyboardDidShow() {
let exitTapGesture = UITapGestureRecognizer(target: self, action: #selector(exitKeyboardTap(_:)))
exitTapGesture.name = "exitTap"
exitTapGesture.delegate = self
self.view.addGestureRecognizer(exitTapGesture)
}
However, the keyboard only dismisses when I tap outside of the coverView. When I tap anywhere inside of the coverView the keyboard does not dismiss.
Try to add gesture to coverView
private func getTapGesture() -> UITapGestureRecognizer {
let exitTapGesture = UITapGestureRecognizer(target: self, action: #selector(exitKeyboardTap(_:)))
exitTapGesture.delegate = self
return exitTapGesture
}
#objc func keyboardDidShow() {
coverView.addGestureRecognizer(getTapGesture())
self.view.addGestureRecognizer(getTapGesture())
}
Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}
You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}
You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}
I have two viewControllers:
ViewController1
A complex stack of sub viewcontrollers with somewhere in the middle an imageView
ViewController2
A scrollView with an imageView embedded in it
What I'm trying to achieve is a transition between the two viewControllers which gets triggered by pinching the imageView from viewController 1 causing it to zoom in and switch over to viewController 2. When the transition has ended, the imageView should be zoomed in as far as it's been zoomed during the pinch gesture triggered transition.
At the same time I want to support panning the image while performing the zoom transition so that just like with the zoom, the image in the end state will be transformed to the place it's been panned to.
So far I've tried the Hero transitions pod and a custom viewController transitions I wrote myself. The problem with the hero transitions is that the image doesn't properly get snapped to the end state in the second viewController. The problem I had with the custom viewController transition is that I couldn't get both zooming and panning to work at the same time.
Does anyone have an idea of how to implement this in Swift? Help is much appreciated.
The question can be divided in to two:
How to implement pinch zoom and dragging using pan gesture on an imageView
How to present a view controller with one of its subviews (imageView in vc2) positioned same as a subview (imageView in vc1) in the presenting view controller
Pinch gesture zoom: Pinch zooming is easier to implement using UIScrollView as it supports it out of the box with out a need to add the gesture recogniser. Create a scrollView and add the view you'd like to zoom with pinch as its subview (scrollView.addSubview(imageView)). Don't forget to add the scrollView itself as well (view.addSubview(scrollView)).
Configure the scrollView's min and max zoom scales: scrollView.minimumZoomScale, scrollView.maximumZoomScale. Set a delegate for scrollView.delegate and implement UIScrollViewDelegate:
func viewForZooming(in scrollView: UIScrollView) -> UIView?
Which should return your imageView in this case and,
Also conform to UIGestureRecognizerDelegate and implement:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
Which should return true. This is the key that allows us have pan gesture recogniser work with the internal pinch gesture recogniser.
Pan gesture dragging: Simply create a pan gesture recogniser with a target and add it to your scroll view scrollView.addGestureRecognizer(pan).
Handling gestures: Pinch zoom is working nicely by this stage except you'd like to present the second view controller when pinching ends. Implement one more UIScrollViewDelegate method to be notified when zooming ends:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
And call your method that presents the detail view controller presentDetail(), we'll implement it in a bit.
Next step is to handle the pan gesture, I'll let the code explain itself:
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
#objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
The implementation moves imageView around following the pan location and calls presentDetail() when gesture ends.
Before we implement presentDetail(), head to the detail view controller and add properties to hold imageViewFrame and the image itself. Now in vc1, we implement presentDetail() as such:
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
// Note that we do not need the animation.
present(detail, animated: false, completion: nil)
}
In your DetailViewController, make sure to set the imageViewFrame and the image in e.g. viewDidLoad and you'll be set.
Complete working example:
class ViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
let imageView: UIImageView = UIImageView()
let scrollView: UIScrollView = UIScrollView()
lazy var pan: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self, action: #selector(panned(_:)))
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = // set your image
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.addSubview(imageView)
view.addSubview(scrollView)
scrollView.frame = view.frame
let w = view.bounds.width - 30 // padding of 15 on each side
imageView.frame = CGRect(x: 0, y: 0, width: w, height: w)
imageView.center = scrollView.center
scrollView.addGestureRecognizer(pan)
}
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
#objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
// MARK: UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
presentDetail()
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// MARK: Private
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
present(detail, animated: false, completion: nil)
}
}
class DetailViewController: UIViewController {
let imageView: UIImageView = UIImageView()
var imageViewFrame: CGRect!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
imageView.frame = imageViewFrame
imageView.image = image
view.addSubview(imageView)
view.addSubview(backButton)
}
lazy var backButton: UIButton = {
let button: UIButton = UIButton(frame: CGRect(x: 10, y: 30, width: 60, height: 30))
button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside)
button.setTitle("back", for: .normal)
return button
}()
#objc func back(_ sender: UIButton) {
dismiss(animated: false, completion: nil)
}
}
seems like UIView.animate(withDuration: animations: completion:) should help you; for example, in animations block you can set new image frame, and in completion: - present second view controller (without animation);
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.
How would I hide the navigation bar once my imageView is tapped, the navigation bar messes up the view of the full screen image once my imageView is tapped and I would like it hidden when the image is tapped and to reappear once the image is dismissed. Here is my code for my image being tapped.
//expandImage
#IBAction func expand(_ sender: UITapGestureRecognizer) {
let imageView = sender.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = self.view.frame
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreen))
newImageView.addGestureRecognizer(tap)
self.view.addSubview(newImageView)
}
func dismissFullscreen(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
Add this to your expand() method:
self.navigationController?.setNavigationBarHidden(true, animated: true)
And in dismissFullscreen() method:
self.navigationController?.setNavigationBarHidden(false, animated: true)
Or you can create new ViewController, pass image to it (with segue e.g) and add this to viewDidLoad() of new ViewController:
self.navigationController?.hidesBarsOnTap = true
So here is how you can do that:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tapImageScrollView = UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapImageScrollView)
}
func imageTapped(_ sender: UIGestureRecognizer) {
if self.navigationController?.navigationBar.isHidden == false {
self.navigationController?.navigationBar.isHidden = true
} else {
self.navigationController?.navigationBar.isHidden = false
}
}
}
So basically add a UITapGestureRecognizer to your imageView and in the imageTapped function you check if the navigationBar is not hidden then you want to show the image and hide the navigationBar and if you click on the imageView again you want to show the navigationBar again.
So simply add the logic in imageTapped to your dismissFullscreen function.