UIScrollView with subviews taps not being excluded if superview is involved - ios

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

Related

How to create extension UITapGestureRecognizerDelegate or fix overlapping view SWIFT

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")
}
}

Th Keyboard does not show up when I press a button once, but when I press a button for a long time

I add a click on uitextfield, the keyboard does not show when touched only with a long press in the field.
What can I do to make the keyboard pop up when I press it and I won't hold it for a long time?
TAP TAP works and deletes other views as well ...
What am I doing wrong?
class DeparturesView: UIViewController {
…
private let searchTextFieldData: UISearchTextField = {
let searchTextFieldData = UISearchTextField()
searchTextFieldData.translatesAutoresizingMaskIntoConstraints = false
searchTextFieldData.keyboardType = .alphabet
searchTextFieldData.borderStyle = .roundedRect
searchTextFieldData.autocorrectionType = .no
searchTextFieldData.spellCheckingType = .no
searchTextFieldData.layer.cornerRadius = 10
searchTextFieldData.backgroundColor = .white
searchTextFieldData.returnKeyType = .search
return searchTextFieldData
}()
}
override func viewDidLoad() {
…
//Mark - Add gesture to app
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == self.view
}
//Mark - Hide keyboard
let hideKeyboards = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:)))
hideKeyboards.delegate = self
view.addGestureRecognizer(hideKeyboards)
//Mark - Detele other view and show keyboard
let hideTableView = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
hideTableView.numberOfTapsRequired = 1
hideTableView.delegate = self
searchTextFieldData.addGestureRecognizer(hideTableView)
}
#objc func tapHandler(_ sender: UITapGestureRecognizer){
print("Show tap tap")
favouriteLabel.removeFromSuperview()
tableViewWithFavouriteBusStop.removeFromSuperview()
}
extension DeparturesView: UITextFieldDelegate{
func textFieldDidBeginEditing(_ textField: UITextField) {
becomeFirstResponder()
}
Follow staps
Remove "hideTableView" TapGesture
add self.earchTextFieldData.delegate = self in viewDidLoad and
replace "textFieldDidBeginEditing"
func textFieldDidBeginEditing(_ textField: UITextField) {
favouriteLabel.removeFromSuperview()
tableViewWithFavouriteBusStop.removeFromSuperview()
becomeFirstResponder()
}

make hitTest(_:with:) function return UIButton

I have a view that added some UIButton.
given a CGPoint how can I get the button laid on.
sample code
let location = tapRecognizer.location(in: view)
let tapView = view.hitTest(location, with: nil)
the code below won't work how can I get the UIbutton form tapView
if let button = tapView as? UIButton {
print("text")
}
help appreciated
While adding button to the view add target to the button and assign a different tag to each button and perform action according to the tag
For the this purpose you can use the gesture delegate like
Example:-
func addGestureOnContentView () -> Void {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ExploreViewController.hideTableView))
tapGesture.delegate = self
self.view.addGestureRecognizer(tapGesture)
}
Now use gesture delegate like :-
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
if (touch.view?.isDescendant(of: UIButton))! {
return false //Perform your task in this block
}
}
return true
}

How to determine if DatePicker is spinning?

How do I determine if a DatePicker is currently spinning/rolling? I have tried just about everything and looked at all the answers on here but none are right or worked.
Essentially, I would like to disable a button while it is spinning then enable the same button when it stopped.
Thanks!
I wanted to do the same thing, and so poked around UIControl and the class dump for UIDatePicker. Nothing seemed to work, as maddy stated in the comment section.
Well, it did occur to me that this is some way to do it - screen shot the picker view over a short time - say 50 ms - and do an image compare between the before and after to see if its changing. That sure seems like a lot of work!
So I experimented with various approaches (iOS13), and I found that I could use a gesture recognizer to detect when the picker was first touched, as well as when the UIDatePicker's own recognizer was updating. This let me generally detect when user interaction likely ended.
Then, I could await the action method signally that the spinning stopped. Ah, but what happens if the user just moved one of the wheels, then put it back? If the new date is the same as the old one, no action method. So what I did was use a delayed dispatch work item, and after two seconds ruled the spinner as stopped.
final class DHDatePicker: UIDatePicker, UIGestureRecognizerDelegate {
var isSpinning = false { didSet { print("IS SPINNING", isSpinning) } }
private var didAddGR = false
private var cancelBlock = DispatchWorkItem(block: {})
override init(frame: CGRect) {
super.init(frame: frame)
let gr = UISwipeGestureRecognizer()
gr.direction = [.up, .down]
gr.delegate = self
self.addGestureRecognizer(gr)
self.addTarget(self, action: #selector(dateChanged(_:)), for: .primaryActionTriggered)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func gr(_ gr: UISwipeGestureRecognizer) {
switch gr.state {
case .ended, .cancelled, .failed:
cancelBlock.cancel()
cancelBlock = DispatchWorkItem(block: {
if self.isSpinning {
self.isSpinning = false
}
})
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: cancelBlock)
print("GR ENDED")
default:
print("WTF")
break
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
isSpinning = true
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if didAddGR == false {
didAddGR = true
print("DP Attach to Other GR")
otherGestureRecognizer.addTarget(self, action: #selector(gr(_:)))
}
return false
}
#objc func dateChanged(_ sender: UIDatePicker) {
print("DP ActionMethod")
cancelBlock.cancel()
isSpinning = false
}
}

Detect UIScreenEdgeGestureRecognizer and fail UIPanGestureRecognizer

In my ViewController I have a UIScreenEdgeGestureRecognizer for switching views but also a UITableView with a custom UITableViewCell. In this custom UITableViewCell is a UIPanGestureRecognizer for swiping the cells.
I added gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: so both of the gestures are working and gestureRecognizerShouldBegin to prevent the conflict of vertical scrolling.
The question is, how can I give the UIScreenEdgeGestureRecognizer priority? When I swipe at the edge of the screen, I want to switch the views without panning the cells. I figured I should be using the delegate methods, but so far the more I read the more confused I'm getting.
Code in custom UITableViewCell:
override func viewDidload() {
var recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
recognizer.delegate = self
addGestureRecognizer(recognizer)
}
override func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {
let velocity = panGesture.velocityInView(superview!)
if fabs(velocity.x) > fabs(velocity.y) { return true }
return false
}
return false
}
ViewController:
override func viewDidLoad() {
let rightScreenEdgeRecognizer = EdgePanGesture(target: self, action: "changeView:")
rightScreenEdgeRecognizer.edges = .Right
view.addGestureRecognizer(rightScreenEdgeRecognizer)
}
I also tried identifying the recognizers with:
gestureRecognizer.isKindOfClass(UIScreenEdgePanGestureRecognizer)
and
gestureRecognizer as? UIScreenEdgePanGestureRecognizer
But all were failed.
Turns out I was looking at the right direction with gestureRecognizer as? UIScreenEdgePanGestureRecognizer but instead of gestureRecognizer I should have used otherGestureRecognizer.
override func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let recognizer = otherGestureRecognizer as? UIScreenEdgePanGestureRecognizer {
return true
}
return false
}

Resources