Reset CollectionviewCell position on tap gesture - swift3

I am working with gestures first time here. Please let me know if my approach is wrong or any better solution.
I am trying to delete the collectionView Cell on swiping Left just like UITableview delete function. Deleting works fine. Now what I want is, Once I swipe the cell and tap anywhere on COllectionView it should swipe back to its original position(same like tableview delete row functionality)
I am using/trying this code
Updated viewDidLoad and tapped event
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(tap)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let Cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CustomCell
Cell.backgroundColor = UIColor.white
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(delete(sender:)))
leftSwipe.direction = UISwipeGestureRecognizerDirection.left
Cell.addGestureRecognizer(leftSwipe)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
Cell.addGestureRecognizer(tap)
Cell.deleteButton.addTarget(self, action: #selector(DeleteCell(sender:)), for: .touchUpInside)
}
func tapped(_ recognizer: UITapGestureRecognizer) {
// self.collectionView.performBatchUpdates({
//self.collectionView.reloadSections(NSIndexSet(index: 0) as IndexSet)
//}, completion: nil)
let point = recognizer.location(in: collectionView)
let indexPath = collectionView.indexPathForItem(at: point)
let cell = self.collectionView.cellForItem(at: indexPath!)
UIView.animate(withDuration: 0.4) {
cell?.contentView.frame = CGRect(x: 0, y: 0, width: (cell?.contentView.frame.width)!, height: (cell?.contentView.frame.height)!)
}
}
func delete(sender: UISwipeGestureRecognizer){
let cell = sender.view as! CustomCell
UIView.animate(withDuration: 0.4) {
cell.contentView.frame = CGRect(x: -90, y: 0, width: cell.contentView.frame.width, height: cell.contentView.frame.height)
}
}
func DeleteCell(sender : AnyObject){
let cell = sender.superview as! CustomCell
let i = self.collectionView.indexPath(for: cell)!.item
let indexpath = self.collectionView.indexPath(for: cell)
let array : NSMutableArray = []
self.collectionView.performBatchUpdates({
self.userArray.remove(at: i)
array.add(indexpath!)
self.collectionView.deleteItems(at:array as! [IndexPath])
}, completion: nil)
}
class CustomCell: UICollectionViewCell {
let deleteButton: UIButton = {
let deleteBtn = UIButton()
deleteBtn.setImage(UIImage(named: "red"), for: .normal)
deleteBtn.contentMode = .scaleAspectFit
return deleteBtn
}()
}
So here I am able to set the cell's position back to original by self.collectionView.performBatchUpdates but its not smooth animation. I tried using
UIView.animate(withDuration: 0.4) {
cell.contentView.frame = CGRect(x: 0, y: 0, width: cell.contentView.frame.width, height: cell.contentView.frame.height)
}
but it works only if swiped cell tapped, not any other cell or anywhere else. Any suggestions would be helpful!!

Right now you are accessing your cell from within itself. The reason it only works to tap on the cell you just swiped is because that is the only cell with that specific instance of UITapGestureRecognizer. To fix this, you should add that tap gesture recognizer to your whole view. Try adding this to your viewDidLoad() method:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(tap)

Finally, got the solution.
Here is the demo project I found - CollectionViewSlideLeft
Hope it will help someone like me. :)

Related

Start SwiftUI implementation of UIScrollView at specific scroll and zoom

I have some experience in SwiftUI, but am new to UIKit.
I'd like to import the zoom and position from one instance of an UIViewRepresentable UIKit ScrollView to another. So, basically, the user scrolls and zooms and later, in another branch of the view hierarchy, I want to start zoomed in at that zoom and position. I can't get it to work though, even after many attempts.
Below is my makeUIView function where I try to set the position and zoom that I want (after some initial setup).
func makeUIView(context: Context) -> UIScrollView {
// set up the UIScrollView
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.bouncesZoom = true
scrollView.delaysContentTouches = false
scrollView.maximumZoomScale = 0.85 * screenScale * 10
scrollView.minimumZoomScale = 0.85 * screenScale
// create a UIHostingController to hold our SwiftUI content
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
scrollView.addSubview(hostedView)
/*
Here I add the zoom and position
*/
scrollView.zoomScale = 0.85 * screenscale
// add zoom and content offset
if let zoomScale = zoomScale, let contentOffset = contentOffset {
scrollView.contentOffset = contentOffset
// make sure it is within the bounds
var newZoomScale = zoomScale
if zoomScale < scrollView.minimumZoomScale {
print("too small")
newZoomScale = scrollView.minimumZoomScale
} else if zoomScale > scrollView.maximumZoomScale {
print("too large")
newZoomScale = scrollView.maximumZoomScale
}
scrollView.setContentOffset(contentOffset, animated: true)
scrollView.setZoomScale(newZoomScale, animated: true)
}
return scrollView
}
The way I get the zoom and contentOffset in the first place is to grab the values from the Coordinator in first ScrollView instance using the below code. As far as I can tell this works well and I get updates with sensible values after zooming or scrolling. The first code snippet contains the makeCoordinator function where I initiate the coordinator with methods from an environmentObject (which then updates said object). The second snippet contains the Coordinator.
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content),
userScrolledAction: drawingModel.userScrollAction,
userZoomedAction: drawingModel.userZoomAction)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
let userScrolledAction: (CGPoint) -> Void
let userZoomedAction: (CGFloat) -> Void
init(hostingController: UIHostingController<Content>, userScrolledAction: #escaping (CGPoint) -> Void, userZoomedAction: #escaping (CGFloat) -> Void) {
self.hostingController = hostingController
self.userScrolledAction = userScrolledAction
self.userZoomedAction = userZoomedAction
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
userScrolledAction(scrollView.contentOffset)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
userZoomedAction(scrollView.zoomScale)
}
}

Presenting image picker using TapGesture on UIImageView

I have a View with an UIImageView that I want to be 'selectable' so that the user can pick a new image.
The function for picking the new image is in the Controller.
Question
How do I call the myDatasourceController.handleTap() function by pressing the ImageView, so that the image picker is presented?
This is an example of my current setup
View
class myView: UICollectionViewCell {
lazy var profileImageView: UIImageView = {
let iv = UIImageView()
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(myDatasourceController.handleTap)))
return iv
}()
}
Controller
class myDatasourceController: UICollectionViewController,
UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
func handleTap(){
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.allowsEditing = true
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// logic for picking the image
dismiss(animated: true, completion: nil)
}
}
This setup currently throws the error
unrecognized selector sent to instance 0x7f9163d493f0
which has led me to try various combinations of
handleTap(_:)
handleTap(sender: UITapGestureRecogniser)
/// etc
but I can't get any of them to work. How should I be constructing my View, Controller, and the interaction between them to present the image picker?
Use Like this
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(RegisterViewController. handleTap(gesture:)))
func handleTap(gesture: UIGestureRecognizer) {
// if the tapped view is a UIImageView then set it to imageview
if (gesture.view as? UIImageView) != nil {
print("Image Tapped")
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
present(picker, animated: true, completion: nil)
}
}
Use like this :
myDatasourceController.handleTap()
In your code :
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(myDatasourceController.handleTap())))
The key to the solution is to implement a protocol / delegate, as suggested by #Akis
I've uploaded the full project to my github account. The key code is copied here.
View Controller
protocol ImagePickerDelegate: class {
func loadImagePicker()
}
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate, ImagePickerDelegate {
let cellId = "cellId"
func loadImagePicker(){
print(" -- image picker -- ")
// load image picker
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.allowsEditing = true
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// get the image
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
}else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
//doSomethingWithTheImage(image: selectedImage)
}
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .black
collectionView?.register(HomeView.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! HomeView
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
View
class HomeView: UICollectionViewCell {
// break retain cycle with weak var
weak var delegate: ImagePickerDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
lazy var profileImageView: UIImageView = {
let iv = UIImageView()
iv.isUserInteractionEnabled = true
iv.image = UIImage(named: "kuang-si-falls-waterfall-water-laos-50588.jpg")
iv.contentMode = .scaleAspectFill
let tap = UITapGestureRecognizer(target: self, action: #selector(loadImagePicker))
iv.addGestureRecognizer(tap)
return iv
}()
func loadImagePicker() {
delegate?.loadImagePicker()
print(" imagePickerProtocol called ")
}
func setupViews() {
backgroundColor = .white
addSubview(profileImageView)
profileImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
profileImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
profileImageView.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I get this error when I try to add TapGestureRecognizer to my UIImageView : Unrecognized selector sent to class

I have this DiaryCell (collectionviewcell) class, and I am trying to add a gesture recognizer in that class to call the method of a collectionViewController, which is a different class.
class DiaryCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//imageview for favorite button
let favoriteImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "favorite_gray")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
//imageview for menu button
let menuImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "menu_image")
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
}
I have tried different strategies, but I am not able to add the gesture recognizer to menuImageView. I have tried doing this, where HomeCollectionViewController is the controller where I want to handle the action when image is tapped.
imageView.addGestureRecognizer(UITapGestureRecognizer(target: HomeCollectionViewController.self, action: #selector(HomeCollectionViewController.menuBarPressed))
The problem is that while your code is legal and compiles, it's wrong.
You are setting up your gesture to be send to a type not an instance. You need to set the target as the actual instance of HomeCollectionViewController
One way to achieve this is by setting a weak reference to your controller in the cell.
class DiaryCell: UICollectionViewCell {
var tapGesture:UITapGestureRecognizer?
weak var gestureTarget: HomeCollectionViewController? {
didSet {
setupGestures()
}
}
func setupGestures() {
if let tapGesture = tapGesture {
removeGestureRecognizer(tapGesture)
self.tapGesture = nil
}
if let gestureTarget = gestureTarget {
let gesture = UITapGestureRecognizer(target: gestureTarget, action: #selector(HomeCollectionViewController.menuBarPressed))
addGestureRecognizer(gesture)
tapGesture = gesture
}
}
}
You would probably set the gestureTarget in your collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell method
While this solves your problem its not a good way to approach things and you should look up how to use delegation as a coding pattern.

Swift 3 NavigationController segue back causes wkwebview to move into wrong position

I have a ViewController containing a WKWebView, the view is positioned correctly the first time it loads, but after moving to another view (I'm opening another view by intercepting links from the WebView) and pressing the navigation item (back button) it briefly appears in the right place, then reloads with the top of the webview behind the navigation bar so the top of the page is cut off.
class HomeVC: BaseViewController, WKNavigationDelegate, WKUIDelegate {
var webView: WKWebView?
override func viewDidAppear(_ animated: Bool) {
self.edgesForExtendedLayout = UIRectEdge.top;
super.viewDidLoad()
addSlideMenuButton()
let screenSize: CGRect = UIScreen.main.bounds
let frameRect: CGRect = CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height)
let url: NSURL = Bundle.main.url(forResource: "services", withExtension: "html")! as NSURL
let requestObj: NSURLRequest = NSURLRequest(url: url as URL);
self.webView = WKWebView(frame: frameRect)
self.webView?.load(requestObj as URLRequest)
self.webView?.navigationDelegate = self
self.webView?.uiDelegate = self
self.view = self.webView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationItem.title = ""
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = "SELECT A SERVICE"
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
let link: String = (webView.url?.absoluteString)!
print(link)
if(link == "file:///haircut") {
print(link)
self.openViewControllerBasedOnIdentifier("WebVC")
}
}
I've searched around and can't find any similar issues, nor can I see anything obvious in the code.
You have are calling super.viewDidLoad from func viewDidAppear(), what can cause unexpected behaviour. Therefore your UIViewController subclass will never notify its superclass, that the view has been loaded.
override func viewDidAppear(_ animated: Bool) {
// Do not do this before calling super!
self.edgesForExtendedLayout = UIRectEdge.top;
// You are calling the wrong the function for super
// It should be super.viewDidAppear(animated)
super.viewDidLoad()
addSlideMenuButton()
let screenSize: CGRect = UIScreen.main.bounds
let frameRect: CGRect = CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height)
let url: NSURL = Bundle.main.url(forResource: "services", withExtension: "html")! as NSURL
let requestObj: NSURLRequest = NSURLRequest(url: url as URL);
self.webView = WKWebView(frame: frameRect)
self.webView?.load(requestObj as URLRequest)
self.webView?.navigationDelegate = self
self.webView?.uiDelegate = self
self.view = self.webView
}

Swift Hides back button

I want to hide the back button and set a title.
I'm using the following code:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Einstellungen"
navigationItem.hidesBackButton = true }
But the title isn't shown and the back button is still there but if I touch it nothing happens. Can anybody help me please?
I found a solution on my own.
If I'm setting the title and the hidesBackButton from my previous ViewController everything works fine.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewControllerFirstSettings {
destinationVC.navigationItem.hidesBackButton = true
destinationVC.navigationItem.title = "Einstellungen"
}
}
This code may help :
// MARK: - CUSTOM METHODS
func createNavBar() {
let leftNavigationButton = UIButton()
leftNavigationButton.setImage(UIImage(named: "ic_back.png"), forState: .Normal)
leftNavigationButton.frame = CGRectMake(10, 10, 20, 15)
leftNavigationButton.addTarget(self, action: "onBackButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
let customBarItem = UIBarButtonItem(customView: leftNavigationButton)
self.navigationItem.leftBarButtonItem = customBarItem;
//set TitleAppIcon
let GR = UITapGestureRecognizer(target: self, action: Selector("headerLogoTapAction:"))
let imageView = UIImageView(frame: CGRect(x: 90, y: 0, width: ((UIScreen.mainScreen().bounds.width)/3), height: 40))
imageView.addGestureRecognizer(GR)
imageView.userInteractionEnabled = true
navigationItem.titleView = imageView
}