I'm having a problem with the pull-down gesture that is not working. I'm presenting an UIPageViewController and each pages is a UIViewController containing an UITableView. when the transition style is set to pageCurl there is not problem with the pull-down gesture to dismiss but when the transition style is set to scroll, I can't dismiss the view. Only the UITableView is scrolling even when at the top
final class HistoryReceiptContainerViewController: UIPageViewController {
private let viewModel: HistoryReceiptContainerViewModel
init(viewModel: HistoryReceiptContainerViewModel) {
self.viewModel = viewModel
super.init(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setViewControllers([receiptViewController(for: HistoryReceiptViewModel(historyId: "1234", source: "", context: nil, contextId: nil))], direction: .forward, animated: true, completion: nil)
}
private func receiptViewController(for viewModel: HistoryReceiptViewModel) -> HistoryReceiptViewController {
let vc = HistoryReceiptViewController(viewModel: viewModel)
return vc
}
}
And the code to present the view
present(HistoryReceiptContainerViewController(viewModel: viewModel)
Do you have any solutions for that kind of problem ?
Thank you in advance!
https://imgur.com/a/rwkVA0Q
Here is an example implementation to fix dismissing a UIPageViewController with transitionStyle .scroll by adding a UIPanGestureRecognizer and some custom gesture recognizer handling. It will work when pushing the UIPageViewController on a navigation stack as well as when presenting it modally.
class DemoPageViewController: UIPageViewController, UIGestureRecognizerDelegate {
var presentedModally = false
init(presentedModally: Bool = false) {
super.init(transitionStyle: .scroll, //if you would use .pageCurl dismissing works
navigationOrientation: .horizontal,
options: nil)
self.presentedModally = presentedModally
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setUpPanGestureForDismissingSelf()
}
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let navigationController = navigationController, navigationController.interactivePopGestureRecognizer == gestureRecognizer else {
return true
}
return navigationController.viewControllers.count > 1
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return !presentedModally
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return presentedModally
}
private func setUpPanGestureForDismissingSelf() {
guard presentedModally else { return }
let panGestureRecognizer = UIPanGestureRecognizer(target: self,
action: #selector(handlePanGesture(sender:)))
panGestureRecognizer.delegate = self
view.addGestureRecognizer(panGestureRecognizer)
}
#objc func handlePanGesture(sender: UIPanGestureRecognizer) {
let dragVelocity = sender.velocity(in: view)
if dragVelocity.y >= 1300 {
dismiss(animated: true)
}
}
}
Related
I create this basic tap gesture function for dismissing view and I added a delegate to override tap when the user taps on another custom sheet (container).
#objc func handleTapGesture() {
dismiss(animated: true, completion: nil)
}
private func tapGestureToDissmis() {
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(handleTapGesture))
tap.delegate = self
view.addGestureRecognizer(tap)
}
extension TextConfigurationVC : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: container) == true {
return false
}
return true
}
My problem is that I want to implement this for 8 screens and I don't want to repeat my self. i create in my UIViewcontroler extension file this tap function and I don't now how to pass this view :
extension UIViewController : UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: **<UIView>)** == true {
return false
}
return true
}
when I created the global container, each time it was called, it was overlapping each other
(tap x 6)
Simple way to handle individual tap action for views
extension UIView {
private struct OnClickHolder {
static var _closure:()->() = {}
}
private var onClickClosure: () -> () {
get { return OnClickHolder._closure }
set { OnClickHolder._closure = newValue }
}
func onTap(closure: #escaping ()->()) {
self.onClickClosure = closure
isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(onClickAction))
addGestureRecognizer(tap)
}
#objc private func onClickAction() {
onClickClosure()
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 50))
view.backgroundColor = .red
view.onTap {
print("View Tapped")
}
}
I presented a view controller and I have a subview in it. I added a swipe gesture on the subview. The swipe gesture is not being called. Instead, the presented view controller is trying to dismiss itself i.e. going down. How do I override the swipe gesture to be recognised by the subview instead of the super view.
When I implemented swipe on a static view controller, it works as expected.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var viewSwipe: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(swipeUp(_:)))
swipeUp.direction = UISwipeGestureRecognizer.Direction.up
self.viewSwipe.addGestureRecognizer(swipeUp)
let swipedown = UISwipeGestureRecognizer(target: self, action: #selector(swipeDown(_:)))
swipedown.direction = UISwipeGestureRecognizer.Direction.down
self.viewSwipe.addGestureRecognizer(swipedown)
}
#objc func swipeUp(_ gesture: UISwipeGestureRecognizer) {
print("swiped up")
}
#objc func swipeDown(_ gesture: UISwipeGestureRecognizer) {
print("swiped down")
}
}
The red area needs to get swipe
Add Gesture only in view and you can also add any direction on gesture. Here i put Small example, you can refer this code for your problem.
Get 1 for Right Direction and 2 For Left Direction
Example :
import UIKit
class ViewController: UIViewController,UIGestureRecognizerDelegate{
#IBOutlet weak var viewGray: UIView!
#IBOutlet weak var viewRed: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
let directions: [UISwipeGestureRecognizer.Direction] = [.right, .left]
for direction in directions {
let gesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(sender:)))
gesture.direction = direction
gesture.delegate = self
self.viewRed.addGestureRecognizer(gesture)
}
}
#objc func handleSwipe(sender: UISwipeGestureRecognizer) {
print(sender.direction)
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Your code should work. If not there, is probably conflict with different gesture recognizer. To solve this conflict, you can set delegate on your swipeUp and swipeDown recognizers:
class ViewController: UIViewController, UIGestureRecognizerDelegate
swipeUp.delegate = self
swipeDown.delegate = self
and try to investigate what is happening in this delegate funcs (with breakpoints or with print, it is up to you):
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
If none of this delegate funcs is called, when you are trying to swipe. Then there is probably different (ie invisible) view over you content.
you code will work. Just try to present your view controller as a full screen in case iOS 13. Refer following code.
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = storyboard.instantiateViewController(withIdentifier: "MyViewController") as! MyViewController
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
So I am trying to create an interface in which there are essentially 3 levels of views possible.
The UIView in charge of deciding the meta-data of new cells
The cells themselves
The background UIScrollView
Everything is a child of the UIScrollView however 1 is displayed on top of 2.
The goal is that all the cells are draggable and the scroll view is scrollable. However when the scroll view is tapped that should trigger the creation of a new node and thus the appearance of view 1 which will disappear upon the press of a submit button.
I have set up just about everything except I have one huge problem. If I tap on a cell I still get the creation view meaning that the TapGestureRecognizer isnt excluding subviews. So here is roughly what is set up:
class Node: UILabel {
var recognizer:UIPanGestureRecognizer?
init() {
self.isExclusiveTouch = true
self.isUserInteractionEnabled = true
recognizer = UIPanGestureRecognizer(target: self, action: #selector(drag))
self.addGestureRecognizer(recognizer!)
}
#objc
func drag(rec: UIPanGestureRecognizer) {
goToPoint(point: rec.location(in: self.superview!))
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
var shouldReceive = false
if let clickedView = touch.view {
if clickedView == self {
shouldReceive = true;
}
}
return shouldReceive
}
}
class NodeCreator: UIView {
var submit: UIButton = UIButton(type: UIButton.ButtonType.roundedRect)
init() {
super.init(frame: CGRect.zero)
submit.setTitle("Submit", for: .normal)
submit.translatesAutoresizingMaskIntoConstraints = false
submit.addTarget(self, action: #selector(press), for: .touchUpInside)
self.addSubview(submit)
}
#objc func press() {
if let d = delegate {
if let t = inputField.text {
d.created(title: t, pt: storeP)
self.removeFromSuperview()
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class TaskView: UIScrollView, CreationDelecate{
func created(title: String, pt: CGPoint) {
let temp = Node()
view.addNode(temp, at: pt)
}
var tapRecognizer:UITapGestureRecognizer?
var nodeSet:[Node] = []
func addNode(_ n1: Node, at: CGPoint = CGPoint.zero) {
nodeSet.append(n1)
n1.goToPoint(point: at)
self.addSubview(n1)
self.bringSubviewToFront(n1)
self.isScrollEnabled = true
self.isUserInteractionEnabled = true
self.isExclusiveTouch = false
self.canCancelContentTouches = false
if (tapRecognizer == nil) {
tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
self.addGestureRecognizer(tapRecognizer!)
}
}
#objc func tap(rec: UITapGestureRecognizer) {
let pos = rec.location(in: self)
let creator = NodeCreator(pt: pos)
creator.delegate = self
self.addSubview(creator)
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
var shouldReceive = false
if let clickedView = touch.view {
if clickedView == self {
shouldReceive = true;
}
}
return shouldReceive
}
}
Why is the TaskView still receiving tap gestures when the Nodes are all exclusive touch.
Code does not work because you did not assigned delegate for gesture recognizers. Delegate functions are implemented in Node and TaskView classes. But you did not declared classes as delegates i.e.
class TaskView: UIScrollView, CreationDelecate, UIGestureRecognizerDelegate
Than assign delegate to gesture recognizer:
recognizer.delegate = self as! UIGestureRecognizerDelegate
Should work
I have an extension of UIViewController as follows to dismiss the keyboard if the user taps on the screen. Within this view I have a scroll containing textfields and two container views. The container views contain a collectionView. I want my overall viewController to dismiss when tapped around but I still want my collectionView's didSelectitemAtIndexPath to trigger. How can I achieve this?
public extension UIViewController {
public func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
public func dismissKeyboard() {
view.endEditing(true)
}
}
and
class RegisterVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
logViewLoad()
self.hideKeyboardWhenTappedAround()
let controller = self.storyboard?.instantiateViewController(withIdentifier: "CollectionViewSearchVC") as! CollectionViewSearchVC
controller.type = "SC"
addChildViewController(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.delegate = self
containerView.addSubview(controller.view)
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
controller.didMove(toParentViewController: self)
subContractorVC = controller
let employeeController = self.storyboard?.instantiateViewController(withIdentifier: "CollectionViewSearchVC") as! CollectionViewSearchVC
employeeController.type = "V"
employeeController.delegate = self
addChildViewController(employeeController)
employeeController.view.translatesAutoresizingMaskIntoConstraints = false
employeeContainerView.addSubview(employeeController.view)
NSLayoutConstraint.activate([
employeeController.view.leadingAnchor.constraint(equalTo: employeeContainerView.leadingAnchor),
employeeController.view.trailingAnchor.constraint(equalTo: employeeContainerView.trailingAnchor),
employeeController.view.topAnchor.constraint(equalTo: employeeContainerView.topAnchor),
employeeController.view.bottomAnchor.constraint(equalTo: employeeContainerView.bottomAnchor)
])
employeeController.didMove(toParentViewController: self)
}
Implement the delegate methods for the GestureRecogniser in your ViewController and check for the class which is being touched, if that is of kind UICollectionView, then ignore that touch and let it do the default behaviour, i.e didSelect otherwise allow tap.
class ParentViewController: UIViewController {
var tap: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
tap = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(tap!)
}
public func hideKeyboardWhenTappedAround() {
dismissKeyboard()
}
#objc public func dismissKeyboard() {
view.endEditing(true)
}
}
class RegisterVC: ParentViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tap?.delegate = self
// YOUR EXISTING CODE HERE
}
// NEW CODE ADDED HERE
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
print("shouldReceive")
print(gestureRecognizer.view as Any) // CHECK FOR WHICH CLASS YOU ARE GETTING HERE WHEN YOU CLICK ON COLLECTIONVIEW
if (gestureRecognizer.view?.isKind(of: UICollectionView.self))! {
return false
}
return true
}
}
Try it and share the results.
You need to create a view behind the child VC's views and add the gesture to it
I am trying to exclude a subview from my TapGestureRecognizer.
It has been created in sotryboard, the delegate is connected to the controller.
In my ViewControler I have the GestureRecognizerDelegate protocol and I ve set my gesture.delegate = self.
Though the shouldReceiveTouch Gesturerecognizer function is not calling, any idea ?
Here is somne of the code :
class DetailedPostViewController: UIViewController, UITextViewDelegate, MKMapViewDelegate, UIGestureRecognizerDelegate {
let circularLike = CircularLike(frame: CGRectZero)
#IBOutlet var gesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
gesture.delegate = self
self.view.addSubview(circularLike)
circularLike.frame = self.view.bounds
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view!.isDescendantOfView(circularLike){
return false
}
return true
}
#IBAction func UserTap(sender: AnyObject) {
if fromUser {
dismissViewControllerAnimated(true, completion: nil)
} else {
performSegueWithIdentifier("userPage", sender: nil)
}
}
#IBAction func hideUnhide(sender: UIGestureRecognizer) {
if hide {
hide=false
unhideUi()
} else {
hide = true
hideUi()
}
}
}
ok, In fact, I just had to set my subview userTouchEnabled parameter to false, and it then triggered the ShouldRecieveTouch Function ...