I have a view controller where I can add several subviews with a LongPressGesture. For each subview I provide a TapGesture that should open a popover for this view (see picture below).
My problem is, that I can only open the popover for the last subview I added. So why can't I interact with the other subviews anymore?
This is my first App in Swift, so it would be nice if someone could help me.
Some code for you:
This is the LongPressGesture on the root view controller that creates a new subview.
#IBAction func onLongPress(_ gesture : UILongPressGestureRecognizer) {
let position: CGPoint = gesture.location(in: view)
if(gesture.state == .began) {
let subview = MySubview(position: position)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
subview.addGestureRecognizer(tapGesture)
view.addSubview(subview)
}
else if (gesture.state == .ended) {
let subview = self.view.subviews.last
self.openContextMenu(for: subview)
}
}
ViewController with subviews:-
When reacting to an event (long press in your case) only the first responder will handle the event. When adding subviews to a view, all the subviews are added one in front of another. So the last added subview will handle the event - if it is listening for the event. If you need a specific subview to handle the event, you can bring it to front as
parentView.bringSubviewToFront(view: subView)
Alternatively, you can also disable other subviews as
subView.isUserInteractionEnabled = false
Ok, I found a solution for my problem. The good thing is, that it only detects touches in the frames of the subviews.
In my root view controller I added:
var isPanGesture = false
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
isPanGesture = true
if let subview = touches.first!.view as? MySubview {
let position = touches.first!.location(in: self.view)
link.center = position
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let subview = touches.first!.view as? MySubview {
if(!isPanGesture) {
self.openContextMenu(for: subview)
}
isPanGesture = false
}
}
Related
So I have a tableviewController called SettingsViewController, and it has the following touchesEnded function:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("A")
super.touchesEnded(touches, with: event)
print("B")
if let touch = touches.first {
print("C")
let touchLocation = touch.location(in: view)
// 290 because the width of the view is 414, and the SettingsViewController width gets set to 0.7 * the view width in SlideInTransition. 0.7 * 414 is 289.8
if touchLocation.x > 290 {
dismiss(animated: true)
}
}
}
I made the print statement to see if it was being called, which it is not. This view controller is presented with a 'menu-esque' slide in custom transition. Is it possible that the bounds of the UIView of the new tablevc displayed with a custom transition is the problem somehow? Regardless, I have tried implementing super.touchesEnded, or adding all the override touches functions and tapped all over the screen with each simulation, but the touchesEnded never gets called. Any idea what's wrong and how to fix it? Thanks
I'm going to guess that you're putting the touchesEnded() func in your table view controller ... which is not going to work. The table view itself is consuming the touches.
This might work for you...
Add this UITableView subclass to your project:
class TouchableTableView: UITableView {
var callback: (() -> ())?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("A")
super.touchesEnded(touches, with: event)
print("B")
if let touch = touches.first {
print("C")
let touchLocation = touch.location(in: self)
// not clear where you're trying to check for touch, so this may need to be changed
// 290 because the width of the view is 414, and the SettingsViewController width gets set to 0.7 * the view width in SlideInTransition. 0.7 * 414 is 289.8
if touchLocation.x > 290 {
// this code is in a UITableView subclass,
// so we can't call dismiss() from here
//dismiss(animated: true)
// instead, we tell the Controller to take action
callback?()
}
}
}
}
Then, in Storyboard, select the Table View in your Table View Controller and assign its Custom Class to TouchableTableView:
Now, touchesEnded() will be called in the custom TouchableTableView class (unless you tap on an interactive UI element in the cell, such as a button).
We then use the "callback" closure to tell the Controller about the touch. So, in your UITableViewController, add this in viewDidLoad():
class ExampleTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// we're in a UITableViewController,
// so make sure the tableView is our TouchableTableView class
// if it is, setup the closure
if let tv = tableView as? TouchableTableView {
tv.callback = {
// here is where we would, for example, call dismiss()
self.dismiss(animated: true, completion: nil)
}
}
}
}
I'm trying to hide a view after it was tapped. In order to do so, I'm using touchesBegan to detect if the view was tapped and if it was, it should perform an action such as hiding the view and the view on top of it. These two views are defined:
#IBOutlet weak var theDarkView: UIView!
#IBOutlet weak var theFinalView: UIView!
And here is the code that I came up with.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//-- hides the view whenever its touched
if let touch = touches.first {
if touch.view == self.theDarkView {
if theDarkView.isHidden == false || theFinalView.isHidden == false {
theDarkView.isHidden = true
theFinalView.isHidden = true
}
} else {
return
}
}
}
If you could help me out and show me the correct way to go about this and also show me where I messed up with my method, that would be greatly appreciated!
No problem with your code this accomplish the same job also
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
theDarkView.isHidden = touches.first?.view == self.theDarkView
theFinalView.isHidden = theDarkView.isHidden
}
Your code works. When you tap on the theDarkView all the views are setted to hidden.
If in your project it doesn't works, you must connect the IBOutlet on the storyboard.
EDIT:
If the views are embedded in a scrollView it doesn't works because the UIScrollView intercepts these touches events.
The solutions are subclass the UIScrollView or use the UITapGestureRecognizer instead of touchesBegan.
A possible code solution is to add in the viewDidLoad this:
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized))
tapGesture.numberOfTapsRequired = 1
tapGesture.isEnabled = true
tapGesture.cancelsTouchesInView = false
theDarkView.addGestureRecognizer(tapGesture)
to add the tapGesture to your view and then add the method:
#objc func tapRecognized() {
if red.isHidden == false || blue.isHidden == false {
red.isHidden = true
blue.isHidden = true
}
}
I am trying to use UITapGestureRecognizer to change a UIView color when the view is tapped, then back to the original white color when space outside the UIView is tapped. I can get the UIView to change colors but I cannot get it to change back to white color.
// viewDidLoad
let tapGestureRegonizer = UITapGestureRecognizer(target: self, action:
#selector(mortgagePenaltyVC.viewCellTapped(recognizer:)))
tapGestureRegonizer.numberOfTapsRequired = 1
mortgageLenerViewCell.addGestureRecognizer(tapGestureRegonizer)
#objc func viewCellTapped (recognizer: UITapGestureRecognizer){
print("label Tapped")
mortgageLenerViewCell.backgroundColor = UIColor.lightGray
}
I think you can use touchesBegan method to get the touch outside of view like below
This code works for me...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
guard let location = touch?.location(in: mortgageLenerViewCell) else { return }
if !labelToClick.frame.contains(location) {
print("Tapped outside the view")
mortgageLenerViewCell.backgroundColor = UIColor.white
}else {
print("Tapped inside the view")
mortgageLenerViewCell.backgroundColor = UIColor.lightGray
}
}
here labelToClick is the label on which you want to click & mortgageLenerViewCell is the superview of labelToClick
Feel free to ask for any query...
Thank you.
hi Im new to programming I was try to recognize the user touch on the screen and close all textfield but when I add a scrollview I won't be able to do that I read a lot in stack overflow like : tap recognizer but I could not do that anymore after adding the textfield I got confused
tell me how can I do it please?
thanks
for dismiss the keyboard is the subview of UIView, then use
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
for dismiss the keyboard is the subview of UIScrollview, then use in here scroll view observes the userInteraction so "By setting userInteractionEnabled to NO for your scroll view".
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.yourSCrollviewName.endEditing(true)
}
for resign the keyboard in various types, the some other type has already answered in SO
update
for hide the keyboard the create the TapGesture for your scrollview
self.ScrollView.isUserInteractionEnabled = true
// ScrollView.keyboardDismissMode = .onDrag
let tap = UITapGestureRecognizer(target: self, action: #selector(doubleTapped))
tap.numberOfTapsRequired = 1
self.ScrollView.addGestureRecognizer(tap)
and call the action as
func doubleTapped() {
// do something cool here
self.ScrollView.endEditing(true)
}
You have to add tap gesture on ScrollView
Add below code in action of gesture:
self.yourSCrollviewName.endEditing(true)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(keyBoardHideOnTap))
tapGesture.numberOfTapsRequired = 1
self.ScrollView.addGestureRecognizer(tapGesture)
func keyBoardHideOnTap() {
// do something cool here
self.ScrollView.endEditing(true)
}
say, I have a button lying under UITableView, how can I click the button through the UITableViewCell but do not trigger the cell click event:
The reason I put the button behind the tableview is that I want to see and click the button under the cell whose color set to be clear, and when I scroll the table, the button can be covered by cell which is not with clear color
I created a sample project and got it working:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tap = UITapGestureRecognizer(target: self, action: #selector(TableViewVC.handleTap))
tap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tap)
}
func handleTap(touch: UITapGestureRecognizer) {
let touchPoint = touch.locationInView(self.view)
let isPointInFrame = CGRectContainsPoint(button.frame, touchPoint)
print(isPointInFrame)
if isPointInFrame == true {
print("button pressed")
}
}
To check of button is really being pressed we need to use long tap gesture:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tap = UILongPressGestureRecognizer(target: self, action: #selector(TableViewVC.handleTap))
tap.minimumPressDuration = 0.01
self.view.addGestureRecognizer(tap)
}
func handleTap(touch: UILongPressGestureRecognizer) {
let touchPoint = touch.locationInView(self.view)
print(" pressed")
if touch.state == .Began {
let isPointInFrame = CGRectContainsPoint(button.frame, touchPoint)
print(isPointInFrame)
if isPointInFrame == true {
print("button pressed")
button.backgroundColor = UIColor.lightGrayColor()
}
}else if touch.state == .Ended {
button.backgroundColor = UIColor.whiteColor()
}
}
Get the touch point on the main view. Then use following method to check the touch point lies inside the button frame or not.
bool CGRectContainsPoint(CGRect rect, CGPoint point)
You can write your custom view to touch button or special view behind the topview
class MyView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
for subview in self.subviews {
if subview is UIButton {
let subviewPoint = self.convertPoint(point, toView: subview)
if subview.hitTest(subviewPoint, withEvent: event) != nil { // if touch inside button view, return button to handle event
return subview
}
}
}
// if not inside button return nomal action
return super.hitTest(point, withEvent: event)
}
}
Then set your controller view to custom MyView class