Intro:
Functionality of adding multiple stickers/emoji to a view.
Setup:
There are 2 view controllers - one to which we're adding stickers, and another with a collectionview of stickers.
Stickers themselves are passed in arrays in 'prepareForSegue' func.
There are 2 arrays, one with just sticker images, another - with UIImageViews - stickers that were already panned, pinched and rotated.
Bug:
After adding 2nd sticker, the AddingStickersVC reappears but previous sticker isn't where we left if. It is pinched and zoomed, but not panned. Also new stickers are sticked to the first one (same frame?).
We can pinch and zoom previous stickers separately from new ones - (nope, they have their own frames), but we can't separate them.
End up having a stack of UIImageViews that takes rotating and pinching separately but pans all together.
Also, speed of panning is increasing after each additional sticker (the panning gesture is added multiple time?).
Hierarchy of views
Stickers are added to 'viewForEmoji' view (viewForImgAndEmoji).
AddingStickersVC:
#IBOutlet weak var viewForImgAndEmoji: UIView!
#IBOutlet weak var mainImg: UIImageView!
#IBOutlet weak var viewForSnapshot: UIView!
var imageData: Data!
var imageItself: UIImage!
var currentUserPostRef: FIRDatabaseReference!
var emojiImage: UIImage!
var geoFire: GeoFire!
var arrayOfEmojis = [UIImage]()
var arrayOfEmojiViews = [UIImageView]()
var n:Int = 1
override func viewDidLoad() {
super.viewDidLoad()
if imageData != nil {
let img = UIImage(data: imageData)
let fixedImg = img!.fixOrientation(img: img!)
mainImg.image = fixedImg
} else if imageItself != nil {
mainImg.image = imageItself
}
if arrayOfEmojiViews.count != 0 {
for emojiView1 in arrayOfEmojiViews {
viewForImgAndEmoji.addSubview(emojiView1)
}
}
// get image out of array.
if arrayOfEmojis.count != 0 {
for emoji in arrayOfEmojis {
let emojiView = UIImageView(image: emoji)
emojiView.tag = n
emojiView.frame = CGRect(x: 153, y: 299, width: 70, height: 70)
emojiView.isUserInteractionEnabled = true
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(recognizer:)))
pan.delegate = self
viewForImgAndEmoji.addGestureRecognizer(pan)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(self.handlePinch(recognizer:)))
pinch.delegate = self
viewForImgAndEmoji.addGestureRecognizer(pinch)
let rotate = UIRotationGestureRecognizer(target: self, action: #selector(self.handleRotate(recognizer:)))
rotate.delegate = self
viewForImgAndEmoji.addGestureRecognizer(rotate)
// check so we won't add previous emoji. only new.
if viewForImgAndEmoji.viewWithTag(n) == nil {
viewForImgAndEmoji.addSubview(emojiView)
}
n += 1
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if arrayOfEmojis.count != 0 {
for j in 1...n {
if var view1 = self.viewForImgAndEmoji.viewWithTag(j) as? UIImageView {
arrayOfEmojiViews.append(view1)
print("Zhenya: views frame is \(view1.frame)")
}
}
}
if segue.identifier == "EmojiCollectionVC" {
if let emojiCollection = segue.destination as? EmojiCollectionVC{
if let image = sender as? UIImage {
emojiCollection.userImage = image
if arrayOfEmojis.count != 0 {
//arrayToStoreEmojis
emojiCollection.arrayToStoreEmojis = arrayOfEmojis
emojiCollection.arrayToStoreEmojiViews = arrayOfEmojiViews
}
}
}
}
}
#IBAction func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.viewForImgAndEmoji)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.viewForImgAndEmoji)
}
#IBAction func handlePinch(recognizer: UIPinchGestureRecognizer) {
let pinchPoint = recognizer.location(in: viewForImgAndEmoji)
let ourEmojiView = viewForImgAndEmoji.hitTest(pinchPoint, with: nil)
ourEmojiView!.transform = ourEmojiView!.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
recognizer.scale = 1
}
#IBAction func handleRotate(recognizer: UIRotationGestureRecognizer){
let rotatePoint = recognizer.location(in: viewForImgAndEmoji)
let ourEmojiView = viewForImgAndEmoji.hitTest(rotatePoint, with: nil)
ourEmojiView!.transform = ourEmojiView!.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
And EmojiCollectionVC:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! EmojiCollectionCell
let chosenEmoji = cell.emojiView.image as UIImage!
arrayToStoreEmojis.append(chosenEmoji!)
performSegue(withIdentifier: "backToEmojiVC", sender: arrayToStoreEmojis)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "backToEmojiVC"{
if let destinationVC = segue.destination as? EmojiVC {
if let array = sender as? [UIImage] {
destinationVC.arrayOfEmojis = arrayToStoreEmojis
destinationVC.arrayOfEmojiViews = arrayToStoreEmojiViews
let data = UIImagePNGRepresentation(userImage)
destinationVC.imageData = data
}
}
}
}
Found solution.
There reason why all new imageViews were stacked - because after panning imageViews didn't change their location in view. The whole f view was moving.
(to find this, spent 8hours tracking changes of frame origins with 'prints' at every step of the program).
And the reason the whole view was moving - because panning gesture was added to the whole view.
So instead of
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(recognizer:)))
pan.delegate = self
viewForImgAndEmoji.addGestureRecognizer(pan)
i needed:
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(recognizer:)))
pan.delegate = self
emojiView.addGestureRecognizer(pan)
What is interesting, for pinching and rotating - we still add them to the whole view that contains emojiView:
viewForImgAndEmoji.addGestureRecognizer(pinch)
...
viewForImgAndEmoji.addGestureRecognizer(rotate)
Related
I have a Child UICollectionViewController where I have an array of images.
When I delete any photo I want to send back that array of updated images to Parent UIViewController.
Also in Child controller I have a programatically view which is called when I click on any image to expand it. When the image is expanded the user can click on a Delete button to delete photos from that array.
My array is updated correctly after delete but I can't manage to send it back to parent for some reasons.
I tried to send it back using Delegates and Protocols.
Here is my code for child controller:
protocol ListImagesDelegate {
func receiveImagesUpdated(data: [String]?)
}
class ListImagesVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
// Properties
var receivedImagesPath: [String]? = []
var fullscreenImageView = UIImageView()
var indexOfSelectedImage = 0
var imagesAfterDelete: [String]? = []
var delegate: ListImagesDefectDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("imagesAfterDelete: \(imagesAfterDelete ?? [])") // I'm getting the right number of images in this array.
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
}
...
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Click on photo: \(indexPath.item + 1)")
if let imagePath = receivedImagesPath?[indexPath.item] {
guard let selectedImage = loadImageFromDiskWith(fileName: imagePath) else {return}
setupFullscreenImageView(image: selectedImage)
indexOfSelectedImage = indexPath.item
}
}
private func setupFullscreenImageView(image: UIImage){
fullscreenImageView = UIImageView(image: image)
fullscreenImageView.frame = UIScreen.main.bounds
fullscreenImageView.backgroundColor = .black
fullscreenImageView.contentMode = .scaleAspectFit
fullscreenImageView.isUserInteractionEnabled = true
self.view.addSubview(fullscreenImageView)
self.navigationController?.isNavigationBarHidden = true
self.tabBarController?.tabBar.isHidden = true
let deleteButton = UIButton(frame: CGRect(x: fullscreenImageView.bounds.maxX - 50, y: fullscreenImageView.bounds.maxY - 75, width: 30, height: 40))
deleteButton.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
deleteButton.backgroundColor = .black
deleteButton.setImage(UIImage(named: "trashIcon"), for: .normal)
deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
fullscreenImageView.addSubview(deleteButton)
}
#objc func deleteButtonTapped(button: UIButton!) {
print("Delete button tapped")
receivedImagesPath?.remove(at: indexOfSelectedImage)
imagesAfterDelete = receivedImagesPath
collectionView.reloadData()
self.navigationController?.isNavigationBarHidden = false
self.tabBarController?.tabBar.isHidden = false
fullscreenImageView.removeFromSuperview()
}
Here is the Parent controller:
var updatedImages: [String]? = []
...
...
extension NewAlbumVC: ListImagesDelegate {
func receiveImagesUpdated(data: [String]?) {
print("New array: \(data ?? [])") // This print is never called.
updatedImages = data
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.delegate = self
}
}
}
I want to specify that my child controller have set a Storyboard ID ("ListImagesID") and also a segue identifier from parent to child ("goToImages"). Can cause this any conflict ?
Thanks if you read this.
It appears that the delegate is nil here
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
For this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
to trigger you must have
self.performSegue(withIdentifier:"goToImages",sender:nil)
Edit: This
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
navigationController?.pushViewController(listImagesDefectVC, animated: true)
doesn't trigger prepareForSegue , so add
listImagesDefectV.delegate = self
So finally
Solution 1 :
#objc func tapOnImageView() {
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}
Solution 2 :
#objc func tapOnImageView() {
self.performSegue(withIdentifier:"goToImages",sender:nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.receivedImagesPath = imagesPath
listImagesVC.delegate = self
}
}
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 want to pass data between my container view and my viewcontroller. Then, I click on the button the label text should be change. The button is in the container view and the label text in the viewcontroller. I tried some solutions here on Stack Overflow but nothing seems to working, or I'm doing it wrong maybe.
Can anyone help?
Gamecontroller.swift
#objc func buttonMove(_ sender:UIButton!) {
print("Button tapped" + String(pointsGame))
let buttonWidth = sender.frame.width;
let buttonHeight = sender.frame.height;
let viewWidth = sender.superview!.bounds.width
let viewHeight = sender.superview!.bounds.height
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
sender.center.x = xoffset + buttonWidth / 2
sender.center.y = yoffset + buttonHeight / 2
pointsGame = pointsGame + 1
}
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "game" {
let vc = segue.destination as! ViewController
vc.gpoints = pointsGame
}
View controller:
class ViewController: UIViewController {
#IBOutlet weak var points: UILabel!
var gpoints: Int!
override func viewDidLoad() {
super.viewDidLoad()
gpoints = 0
points.text = String(gpoints)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Gamecontroller.swift
////// declare object first
var vc = ViewController()
#objc func buttonMove(_ sender:UIButton!) {
print("Button tapped" + String(pointsGame))
let buttonWidth = sender.frame.width;
let buttonHeight = sender.frame.height;
let viewWidth = sender.superview!.bounds.width
let viewHeight = sender.superview!.bounds.height
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
sender.center.x = xoffset + buttonWidth / 2
sender.center.y = yoffset + buttonHeight / 2
pointsGame = pointsGame + 1
vc.refreshView((points : pointsGame)
}
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "game" {
vc = segue.destination as! ViewController
vc.gpoints = pointsGame
}
View controller:
class ViewController: UIViewController {
#IBOutlet weak var points: UILabel!
var gpoints: Int!
override func viewDidLoad() {
super.viewDidLoad()
gpoints = 0
}
func refreshView(points : Int! )
{
points.text = String(points)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Its not a good Approach to use viewController globally, Best way is to use delegates or notifications.Here is notification example when you Tap on button just post notification after pointsGame = pointsGame + 1
// Post notification
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "buttonpressed"), object: nil , userInfo: ["points": 20])
And in View controller ViewDidLoad add observer for notification
let name = Notification.Name("buttonpressed")
NotificationCenter.default.addObserver(self, selector: #selector(refreshLabel(object:)), name: name, object: nil)
func refreshLabel(_ notification: Notification) {
if let myDict = notification.object as? [String: Any] {
if let point = myDict["points"] as? Int {
print(point)
points.text = String(point)
}
}
Step 1: Create One Variable of your container view controller.
var templateVC : GPTemplateVC?
Step 2: In Prepare for a segue do this.
if segue.identifier == "embedSegueTemplateVC" {
templateVC = segue.destination as? GPTemplateVC
}
Then you can access every element from the ContainerVC.
You can also use a delegate for the same.
Just confirm your delegate with your main view controller and call delegate method using delegate object in Container view controller. In your case call it on button click.
I'm trying to update text of a label after a scroll event. I have a print command that prints the correct value but the label is not updating.
Here's my code
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)") // this prints correct value
self.signalLabel.text = signalText[Int(x/w)] // this does not update
}
what's the deal?
Here's the complete view controller code. This view is called from a button click on the initial view controller. This view contains a UIScrollView and UIPageControl. The UIScrollView contains two images that can be scrolled back and forth. I want to update the label text based on image that is shown.
import UIKit
class SignalOneViewController: UIViewController, UIScrollViewDelegate {
// MARK: Properties
#IBOutlet weak var signalScrollView: UIScrollView!
#IBOutlet weak var signalPageControl: UIPageControl!
#IBOutlet weak var signalLabel: UILabel!
// MARK: - Button Actions
#IBAction func signalOneButton(_ sender: Any) {
print("signal one button clicked")
performSegue(withIdentifier: "SignalOneSegue", sender: self)
}
#IBAction func onCancelButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
let signalImages = ["signal1a.png", "signal1b.png"]
let signalText = ["Ready for play", "Untimed down"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
self.loadScrollView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadScrollView() {
let pageCount : CGFloat = CGFloat(signalImages.count)
signalLabel.text = signalText[0]
signalScrollView.backgroundColor = UIColor.clear
signalScrollView.delegate = self
signalScrollView.isPagingEnabled = true
signalScrollView.contentSize = CGSize(width: signalScrollView.frame.size.width * pageCount, height: signalScrollView.frame.size.height)
signalScrollView.showsHorizontalScrollIndicator = false
signalScrollView.showsVerticalScrollIndicator = false
signalPageControl.numberOfPages = Int(pageCount)
signalPageControl.pageIndicatorTintColor = UIColor.lightGray
signalPageControl.currentPageIndicatorTintColor = UIColor.blue
signalPageControl.addTarget(self, action: #selector(self.pageChanged), for: .valueChanged)
for i in 0..<Int(pageCount) {
print(self.signalScrollView.frame.size.width)
let image = UIImageView(frame: CGRect(x: self.signalScrollView.frame.size.width * CGFloat(i), y: 0, width: self.signalScrollView.frame.size.width, height: self.signalScrollView.frame.size.height))
image.image = UIImage(named: signalImages[i])!
image.contentMode = UIViewContentMode.scaleAspectFit
self.signalScrollView.addSubview(image)
}
}
//MARK: UIScrollView Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewWidth: CGFloat = scrollView.frame.size.width
// content offset - tells by how much the scroll view has scrolled.
let pageNumber = floor((scrollView.contentOffset.x - viewWidth / 50) / viewWidth) + 1
signalPageControl.currentPage = Int(pageNumber)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)")
self.signalLabel.text = signalText[p]
print(">>> \(signalText[Int(x/w)])")
}
//MARK: page tag action
#objc func pageChanged() {
let pageNumber = signalPageControl.currentPage
var frame = signalScrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageNumber)
frame.origin.y = 0
signalScrollView.scrollRectToVisible(frame, animated: true)
}
}
Make sure signalLabe IBOutlet is attached to your label in storyboard or xib
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.