How to click a button lying below UITableView - ios

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

Related

Detect which `View` was clicked on a `Cell`

i have three UIImageView on a single Cell when i click on any of the UIImageView on the cell i want to detect which one was clicked on onCellSelection, without placing a UITapGestureRecognizer on each UIImageview
func SocialViewRow(address: SocialMedia)-> ViewRow<SocialMediaViewFile> {
let viewRow = ViewRow<SocialMediaViewFile>() { (row) in
row.tag = UUID.init().uuidString
}
.cellSetup { (cell, row) in
// Construct the view
let bundle = Bundle.main
let nib = UINib(nibName: "SocialMediaView", bundle: bundle)
cell.view = nib.instantiate(withOwner: self, options: nil)[0] as? SocialMediaViewFile
cell.view?.backgroundColor = cell.backgroundColor
cell.height = { 50 }
print("LINK \(address.facebook?[0] ?? "")")
cell.view?.iconOne.tag = 90090
//self.itemDetails.activeURL = address
let openFace = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openFace))
let openT = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openTwit))
let you = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openYouYub))
cell.view?.iconOne.addGestureRecognizer(openFace)
cell.view?.iconTwo.addGestureRecognizer(openT)
cell.view?.iconThree.addGestureRecognizer(you)
cell.frame.insetBy(dx: 5.0, dy: 5.0)
cell.selectionStyle = .none
}.onCellSelection() {cell,row in
//example
//print(iconTwo was clicked)
}
return viewRow
}
Using UITapGestureRecogniser (or UIButton) would be a better approach. These classes intended for tasks like this.
If you still want to use different approach, add method to your cell subclass (replace imageView1, imageView2, imageView3 with your own properties)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point = touch.location(in: view)
if imageView1.frame.containsPoint(point) {
// code for 1st image view
} else if imageView2.frame.containsPoint(point) {
// code for 2nd image view
} else if imageView3.frame.containsPoint(point) {
// code for 3rd image view
}
}
Docs:
location(ofTouch:in:)
contains(_ point: CGPoint)
Override the touchesbegan function. This method is called every time the user touches the screen. Every time it is called, check to see if the touches began in the same location an image is.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: self)
//check here, include code that compares the locations of the image
}
Location will be a CGPoint. You should be able to get the CGPoints for the bounds of your images and then determine if the touchBegan in those bounds. If you want to include the entire path the user touched, there are ways to do that too but the beginning touch should be sufficient for what you want.

change view colour on tap then back to original colour

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.

How prevent a parent UIView to receive touch from UIButton?

I have an UIView (from UIViewController) that receive touch and remove keyboard. In the same screen I've a UIButton that show the password, when I touch in UIButton the touch goes to UIView and remove keyboard.
How prevent the UIView to receive touch from UIButton?
I'd try .isExclusiveTouch but didn't work.
EDIT
My extension that remove the keyboard:
import UIKit
public extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(UIViewController.dismissKeyboard)
)
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
view.endEditing(true)
}
}
In my ViewController, the button:
self.myButton.addTarget(self, action: #selector(self.myButtonHandler), for: .touchUpInside)
self.view.addSubview(self.myButton)
Don't use gestures just use this code
override func touchesBegan(_ touches: Set<UITouch>, with event:UIEvent?) {
if let touch = touches.first {
view.endEditing(true)
}
super.touchesBegan(touches, with: event)
}
Here when you touch any part of view (not buttons) keyboard will be dismissed by code (view.endEditing(true))
Try setting userIntrecationEnabled to false.
//example
yourView.isUserInteractionEnabled = false
If user interaction is disabled for a view, it will ignore touches.

How do I trigger a collection view's didSelectItemAtIndexPath: on tap while it's scrolling?

The default behavior while a collection view is mid-scroll:
tap #1: stops the scrolling
tap #2: triggers didSelectItemAtIndexPath
What I want while a collection view is mid-scroll:
tap #1: triggers didSelectItemAtIndexPath
What would be a clean, correct approach to achieve this? FWIW, I realize this might be unexpected behavior.
I think the best approach is to use the UICollectionView addGestureRecognizer to add a touch gesture recognizer, then process the touch gesture (e.g. get the touch location in the collection view, use that to get the indexPath of the item that was touched, then call the collectionView.didSelectItemAtIndexPath yourself). As for the scrolling, you could use the UISrollViewDelegate methods to disable user interaction on the collection view once the scroll starts, then re-enable user interaction on the collection view once the scrolling stops and/or in the viewDidDisappear view controller function.
Like this:
public class MyViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var collectionViewTap:UITapGestureRecognizer?
override public func viewDidLoad() {
collectionViewTap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(collectionViewTap!)
}
override public func viewDidDisappear(animated: Bool) {
collectionView.userInteractionEnabled = true
}
func handleTap (sender:UITapGestureRecognizer) {
let touchPoint = sender.locationOfTouch(0, inView: collectionView)
let indexPath = collectionView.indexPathForItemAtPoint(touchPoint)
if (indexPath != nil) {
collectionView(collectionView, didSelectItemAtIndexPath: indexPath!)
}
}
public func scrollViewWillBeginDragging(scrollView: UIScrollView) {
collectionView.userInteractionEnabled = false
}
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
collectionView.userInteractionEnabled = true
}
}
In your initializer for the collection view, add an additional target for the pan gesture
self.panGestureRecognizer.addTarget(self, action: #selector(allowSelectionOfItemDuringScroll(_:)))
Then, you can implement it like this:
#objc private func allowSelectionOfItemDuringScroll(_ sender: UIPanGestureRecognizer) {
let yTranslation = sender.translation(in: self).y
var isScrolling: Bool {
if sender.state == .began {
return false
}
if isDragging && isDecelerating {
return false
}
return isDragging || isDecelerating
}
if yTranslation == 0 && isScrolling {
let selectionPoint = sender.translation(in: self)
if let index = indexPathForItem(at: selectionPoint) {
self.delegate?.collectionView?(self, didSelectItemAt: index)
}
}

Touch up and Touch down action for UIImageView

what i want to achieve is when user touch on UIImageView set Image1, when user lifts his finger set Image2.
i can only get UIGestureRecognizerState.Ended with this code
var tap = UITapGestureRecognizer(target: self, action: Selector("tappedMe:"))
imageView.addGestureRecognizer(tap)
imageView.userInteractionEnabled = true
func tappedMe(gesture: UITapGestureRecognizer)
{
if gesture.state == UIGestureRecognizerState.Began{
imageView.image=originalImage
dbgView.text="Began"
}else if gesture.state == UIGestureRecognizerState.Ended{
imageView.image=filteredImage
dbgView.text="Ended"
}else if gesture.state == UIGestureRecognizerState.Cancelled{
imageView.image=filteredImage
dbgView.text="Cancelled"
}else if gesture.state == UIGestureRecognizerState.Changed{
imageView.image=filteredImage
dbgView.text="Changed"
}
}
The UITapGestureRecognizer doesn't change it's state to .Began, but the UILongPressGestureRecognizer does. If you for some reason font want to override the touch callbacks directly you could use a UILongPressGestureRecognizer with a very short minimumPressDuration of like 0.1 to achieve the effect.
Example by #Daij-Djan:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var tap = UILongPressGestureRecognizer(target: self, action: Selector("pressedMe:"))
tap.minimumPressDuration = 0
self.view.addGestureRecognizer(tap)
self.view.userInteractionEnabled = true
}
func pressedMe(gesture: UITapGestureRecognizer) {
if gesture.state == .Began{
self.view.backgroundColor = UIColor.blackColor()
} else if gesture.state == .Ended {
self.view.backgroundColor = UIColor.whiteColor()
}
}
}
Here is the solution:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//began
}
}
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//end
}
}
super.touchesEnded(touches, with: event)
}
Note: Put this inside a UIView SubClass and add: userInteractionEnabled = true inside the init block
The best and most practical solution would be to embed your UIImageView within a UIControl subclass.
By default UIImageView has user interaction enabled. If you create a simple UIControl subclass you can easily add your image view into it and then use the following methods to achieve what you want:
let control = CustomImageControl()
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
The advantages of doing it this way is that you get access to all of the different touch events saving you time from having to detect them yourself.
Depending on what you want to do, you could also override var highlighted: Bool or var selected: Bool to detect when the user is interacting with the image.
It's better to do it this way so that your user has a consistent user experience with all the controls in their app.
A simple implementation would look something like this:
final class CustomImageControl: UIControl {
let imageView: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
// setup our image view
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
imageView.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
imageView.trailingAnchor.constraintEqualToAnchor(trailingAnchor).active = true
imageView.topAnchor.constraintEqualToAnchor(topAnchor).active = true
imageView.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let control = CustomImageControl()
control.translatesAutoresizingMaskIntoConstraints = false
control.imageView.image = ... // set your image here.
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
view.addSubview(control)
control.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
control.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
}
#objc func imageTouchedDown(control: CustomImageControl) {
// pressed down on the image
}
#objc func imageTouchedUp(control: CustomImageControl) {
// released their finger off the image
}
}

Resources