NSTableView: detecting a mouse click together with the row and column - cocoa

I'm trying to detect when a mouse click occurs in an NSTableView, and when it does, to determine the row and column of the cell that was clicked.
So far I've tried to use NSTableViewSelectionDidChangeNotification, but there are two problems:
It only triggers when the selection changes, whereas I want every mouse click, even if it is on the currently selected row.
The clickedRow and clickedColumn properties of NSTableView are both -1 when my delegate is called.
Is there a better (and correct) way of doing this?

There is a simple way.
Tested with Swift 3.0.2 on macOS 10.12.2 and Xcode 8.2.1
Let
tableView.action = #selector(onItemClicked)
Then
#objc private func onItemClicked() {
print("row \(tableView.clickedRow), col \(tableView.clickedColumn) clicked")
}

To catch the user clicking a row (only, when the user clicks a row, not when it is selected programmatically) :
Subclass your NSTableView and declare a protocol
MyTableView.h
#protocol ExtendedTableViewDelegate <NSObject>
- (void)tableView:(NSTableView *)tableView didClickedRow:(NSInteger)row;
#end
#interface MyTableView : NSTableView
#property (nonatomic, weak) id<ExtendedTableViewDelegate> extendedDelegate;
#end
MyTableView.m
Handle the mouse down event (note, the delegate callback is not called when the user clicks outside, maybe you want to handle that too, in that case, just comment out the condition "if (clickedRow != -1)")
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint globalLocation = [theEvent locationInWindow];
NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
NSInteger clickedRow = [self rowAtPoint:localLocation];
[super mouseDown:theEvent];
if (clickedRow != -1) {
[self.extendedDelegate tableView:self didClickedRow:clickedRow];
}
}
Make your WC, VC conform to ExtendedTableViewDelegate.
#interface MyViewController : DocumentBaseViewController<ExtendedTableViewDelegate, NSTableViewDelegate, NSTableViewDataSource>
set the extendedDelegate of the MyTableView to your WC, VC (MyViewController)
somewhere in MyTableView.m
self.myTableView.extendedDelegate = self
Implement the callback in delegate (MyViewController.m)
- (void)tableView:(NSTableView *)tableView didClickedRow:(NSInteger)row {
// have fun
}

I would prefer doing as follows.
Override
-(BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row;
Provide super implementation;
RequiredRow = row;
RequiredColumn = [tableView clickedColumn];
Hope this helps.

If someone is looking for a Swift 3/4/5 version of Peter Lapisu's answer:
Add an extension for the NSTableView (NSTableView+Clickable.swift):
import Foundation
import Cocoa
extension NSTableView {
open override func mouseDown(with event: NSEvent) {
let globalLocation = event.locationInWindow
let localLocation = self.convert(globalLocation, from: nil)
let clickedRow = self.row(at: localLocation)
super.mouseDown(with: event)
if (clickedRow != -1) {
(self.delegate as? NSTableViewClickableDelegate)?.tableView(self, didClickRow: clickedRow)
}
}
}
protocol NSTableViewClickableDelegate: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, didClickRow row: Int)
}
Then to use it, make sure you implement the new delegate protocol:
extension MyViewController: NSTableViewClickableDelegate {
#nonobjc func tableView(_ tableView: NSTableView, didClickRow row: Int) {
Swift.print("Clicked row \(row)")
}
}
The #nonobjc attribute silences the warning about it being close to didClick.

Just in case someone was looking for it in SWIFT and / or for NSOutlineView.
Based on #Peter Lapisu instructions.
class MYOutlineViewDelegate: NSOutlineView, NSOutlineViewDelegate,NSOutlineViewDataSource{
//....
}
extension MYOutlineViewDelegate{
func outlineView(outlineView: NSOutlineView, didClickTableRow item: AnyObject?) {
//Click stuff
}
override func mouseDown(theEvent: NSEvent) {
let globalLocation:NSPoint = theEvent.locationInWindow
let localLocation:NSPoint = self.convertPoint(globalLocation, fromView: nil)
let clickedRow:Int = self.rowAtPoint(localLocation)
super.mouseDown(theEvent)
if (clickedRow != -1) {
self.outlineView(self, didClickTableRow: self.itemAtRow(clickedRow))
}
}}

see the tableViewSelectionIsChanging notification, here are the the comments from NSTableView.h
/*
Optional - Called when the selection is about to be changed, but note, tableViewSelectionIsChanging: is only called when mouse events are changing the selection and not keyboard events.
*/
I concede that this might not be the surest way to correlate your mouse clicks, but it is another area to investigate, seeing that you are interested in mouse clicks.

Related

View-Based NSTableView in Swift - How to

I have a NSTableView whose cells are view-based.
DataSource & Delegate are connected, but I'm not able to display the cell's textField string value.
This is the code in Objective-C, working:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 10;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *cell = [tableView makeViewWithIdentifier:#"List" owner:self];
[cella.textField setStringValue:"Hey, this is a cell"];
return cell;
}
And here is my code in Swift, not working :
func numberOfRowsInTableView(aTableView: NSTableView!) -> Int
{
return 10 //Casual number
}
func tableView(tableView: NSTableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> NSTableCellView! {
var cell = tableView.makeViewWithIdentifier("List", owner: self) as NSTableCellView!
// setup cell without force unwrapping it
cell.textField.stringValue = "Hey, this is a cell"
println("Method called") //Never printed
return cell
}
This is the result: (table on right side of image)
Note that the comment //setup cell without force unwrapping it makes no sense, I forgot to delete it.
What I am missing ?
Edit: I tried even the following with no success:
func numberOfRowsInTableView(aTableView: NSTableView!) -> Int
{
return 10
}
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject
{
var cell = tableView.makeViewWithIdentifier("List", owner: self) as NSTableCellView
cell.textField.stringValue = "Hey this is a cell"
return cell;
}
Thank you all.
Alberto
After hours of search, I discovered this method that works !
func tableView(tableView: NSTableView, viewForTableColumn: NSTableColumn, row: Int) -> NSView
{
var cell = tableView.makeViewWithIdentifier("List", owner: self) as NSTableCellView
cell.textField.stringValue = "Hey, this is a cell"
return cell;
}
I see that you found your answer your self but from what I can see your clue was in the Return Value of Objective -C delegate.
- (NSView *)tableView:...
The return value is a NSView.
But you should look at the Swift/Objective -c documentaion.
From the Docs:
Providing Views for Rows and Columns
tableView:viewForTableColumn:row:
Asks the delegate for a view to display the specified row and column.
Declaration
SWIFT
#optional func tableView(_ tableView: NSTableView!,
viewForTableColumn tableColumn: NSTableColumn!,
row row: Int) -> NSView!
OBJECTIVE-C
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
Note the -> NSView! in the swift code also.
The new docs allow you to see the code for Swift and Objective -c side by side or one or the other. You can use a selection tab at the top of the documentation to choose.
It also looks like your code should include the "!" for optionals
If you change the content mode of the table view from view based to cell based the method is called.
This beat me up for an hour. Works with Swift 2.2, probably won't work for earlier or later versions:
let cell = tableView.makeViewWithIdentifier(myid!, owner: nil) // You can't cast this with as! like they want you to
if let mycell = cell as? NSTableCellView {
mycell.textField?.stringValue = text
return mycell
}

NSImageView double click action

I have some NSImageView in my Mac App where the user can drag'n drop objects like .png or .pdf, to store them into User Shared Defaults, that works fine.
I would now like to set an action for when user double click on these NSImageView, but it seems to be a little bit difficult (I had no trouble for NSTableView, but 'setDoubleAction' is not available for NSImage, and tons of answers (here or with google) concerning NSImageView's actions point to making a NSButton instead of NSImageView, so that doesn't help)
Here is part of my AppDelegate.h:
#interface AppDelegate : NSObject <NSApplicationDelegate>{
(...)
#property (assign) IBOutlet NSImageView *iconeStatus;
(...)
#end
and here is part of my AppDelegate.m:
#import "AppDelegate.h"
#implementation AppDelegate
(...)
#synthesize iconeStatus = _iconeStatus;
(...)
- (void)awakeFromNib {
(...)
[_iconeStatus setTarget:self];
[_iconeStatus setAction:#selector(doubleClick:)];
(...)
}
(...)
- (void)doubleClick:(id)object {
//make sound if that works ...
[[NSSound soundNamed:#"Basso"] play];
}
But that doesn't work.
Can anyone tell me what's the easiest way to do this ?
You need to subclass NSImageView and add the following method to your subclass's implementation:
- (void)mouseDown:(NSEvent *)theEvent
{
NSInteger clickCount = [theEvent clickCount];
if (clickCount > 1) {
// User at least double clicked in image view
}
}
Code for Swift 4. Again the NSImageView is subclassed and the mouseDown function is overridden.
class MyImageView: NSImageView {
override func mouseDown(with event: NSEvent) {
let clickCount: Int = event.clickCount
if clickCount > 1 {
// User at least double clicked in image view
}
}
}
Another solution using extension:
extension NSImageView {
override open func mouseDown(with event: NSEvent) {
// your code here
}
}
Although this will add that functionality to every NSImageView, so perhaps that's not what you're looking for.

which method is called when selecting a cell in the NSTableView in Cocoa OS X?

I have a NSTableView , i want to get the value present in the cell. I am having only one column so , i just need the row number
i can use this [tableView selectedRow]- but where do i put this i want to put this in a method that gets called on selection of any of the rows.
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
NSLog(#"%d",[tableViewController selectedRow]);
}
The above method also does not work i am getting the error
-[NSScrollView selectedRow]: unrecognized selector sent to instance 0x100438ef0]
i want something like the method available in the iPhone tableview-
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
What is tableViewController object? Only NSTableView instances respond to selectedRow. You can get current table view (the one that sent the notification) from notification's object property:
Objective-C:
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
NSLog(#"%d",[[notification object] selectedRow]);
}
Swift:
func tableViewSelectionDidChange(notification: NSNotification) {
let table = notification.object as! NSTableView
print(table.selectedRow);
}
my 2 cents for Xcode 10/swift 4.2
func tableViewSelectionDidChange(_ notification: Notification) {
guard let table = notification.object as? NSTableView else {
return
}
let row = table.selectedRow
print(row)
}
In Swift 5.4:
Just use tableView.action = #selector(YOUR_METHOD), then in your method, try to do something with tableView.selectedRow.
This would simply do the trick. Take a look at the demo below:
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
tableView.action = #selector(didSelectRow)
}
#objc private func didSelectRow() {
print("Selected row at \(tableView.selectedRow)")
}
But please be aware that if nothing is selected, or you clicked outside of a cell, then you'll get a tableView.selectedRow of -1. So you might wanna check if the index is out of range before you use it. :)
Swift 3 (from Eimantas' answer):
func tableViewSelectionDidChange(_ notification: NSNotification) {
let table = notification.object as! NSTableView
print(table.selectedRow);
}
You should addObserver Notification like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tableViewSelectionDidChangeNotification)
name:NSTableViewSelectionDidChangeNotification object:nil];
It will action when tableView selected row
good luck
full guide In Swift 5:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.didSelectRow(_:)), name: NSTableView.selectionDidChangeNotification, object: tableView)
}
#objc
func didSelectRow(_ noti: Notification){
guard let table = noti.object as? NSTableView else {
return
}
let row = table.selectedRow
print(row)
}
deinit {
NotificationCenter.default.removeObserver(self)
}

Simple mouseover effect on NSButton

I am creating a custom NSButtonCell for a custom rendering.
Now, I want to have different aspect depending if the mouse is over the button or not. How can I get this information?
Thanks and regards,
Here is i have created and worked for me perfectly...
Step 1: Create the Button with tracking area
NSButton *myButton = [[NSButton alloc] initWithFrame:NSMakeRect(100, 7, 100, 50)];
[myButton setTitle:#"sample"];
[self.window.contentView addSubview:myButton];
// Insert code here to initialize your application
NSTrackingArea* trackingArea = [[NSTrackingArea alloc]
initWithRect:[myButton bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways
owner:self userInfo:nil];
[myButton addTrackingArea:trackingArea];
Step: 2 Implement the following methods
- (void)mouseEntered:(NSEvent *)theEvent{
NSLog(#"entered");
[[myButton cell] setBackgroundColor:[NSColor blueColor]];
}
- (void)mouseExited:(NSEvent *)theEvent{
[[myButton cell] setBackgroundColor:[NSColor redColor]];
NSLog(#"exited");
}
Swift 3:
Create the button with code or just use it's #IBOutlet.
Then define the button's tracking area for mouse over (hover):
let area = NSTrackingArea.init(rect: yourButtonName.bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: self,
userInfo: nil)
yourButtonName.addTrackingArea(area)
Then override mouseEntered and mouseExited, set whatever you want to change (button color, button image, button text,..) in these functions:
override func mouseEntered(with event: NSEvent) {
print("Entered: \(event)")
}
override func mouseExited(with event: NSEvent) {
print("Exited: \(event)")
}
If you have multiple buttons (with tracking area added for each) and you need to identify which button triggered the mouseEntered event, you can add some userInfo information for this purpose, so instead of:
userInfo: nil
Add your custom button name in userInfo for each button, for example:
userInfo: ["btnName": "yourButtonName"]
Then you can write a switch-case or if statements in your mouseEntered and mouseExited functions like this:
override func mouseEntered(with event: NSEvent) {
// Identify which button triggered the mouseEntered event
if let buttonName = event.trackingArea?.userInfo?.values.first as? String {
switch (buttonName) {
case "yourButtonName":
//do whatever you want for this button..
case "anotherButtonName":
//do whatever you want for this button..
default:
print("The given button name: \"\(buttonName)\" is unknown!")
}
}
}
You need to Subclass the NSButton class (or even better the NSButtonCell class).
As Justin said if you put the two method
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
They should get called when the mouse enter and exit the area. You may also need to re create the tracking area, look here:
- (void)updateTrackingAreas
For fade in and fade out effect I played with animator and alpha value for example:
[self animator]setAlphaValue:0.5];
Swift 5 version with callback, super easy to use:
final class HoverButton: NSButton {
private let callback: (HoverButton, Bool) -> Void
init(callback: #escaping (HoverButton, Bool) -> Void) {
self.callback = callback
super.init(frame: .zero)
let area = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self, userInfo: nil)
addTrackingArea(area)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
callback(self, true)
}
override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
callback(self, false)
}
}
Usage:
let button = HoverButton { button, isHovered in
button.animator().alphaValue = isHovered ? 1 : 0.5
}
a good starting point, declared in NSResponder:
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
specifically, the button cell's container (not the cell itself) is the NSResponder.
For those who prefer subclassing, you can also make your own NSButton and assigning the NSTrackingArea in it.
Here is a really simple and elegant way to do it, thanks to Joey Zhou : https://github.com/Swift-Kit/JZHoverNSButton
It is written in Swift 2 but XCode will automatically translate it in Swift 3 - 4 without any issue.
Hope it can help someone
This is a simple code to track mouse enter and mouse exit events on some controls (Images, Label, etc...):
#IBOutlet weak var myImage: NSImageView!
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let area1 = myTrakingArea(control: self.myImage)
let area2 = myTrakingArea(control: self.myLabel)
self.myImage.addTrackingArea(area1)
self.myLabel.addTrackingArea(area2)
}
func myTrakingArea(control: NSControl) -> NSTrackingArea {
return NSTrackingArea.init(rect: control.bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: control,
userInfo: nil)
}
override func mouseEntered(with event: NSEvent) {
if let owner = event.trackingArea?.owner as? NSControl {
let id : String = owner.identifier!.rawValue
switch id {
case self.myLabel.identifier!.rawValue:
print("Entered Quit Label")
case self.myImage.identifier!.rawValue:
print("Entered Quit Image")
default:
print("Entered ???")
}
}
}
override func mouseExited(with event: NSEvent) {
if let owner = event.trackingArea?.owner as? NSControl {
let id : String = owner.identifier!.rawValue
switch id {
case self.myLabel.identifier!.rawValue:
print("Exited Quit Label")
case self.myImage.identifier!.rawValue:
print("Exited Quit Image")
default:
print("Exited ???")
}
}
}

iOS - forward all touches through a view

I have a view overlayed on top of many other views. I am only using the overaly to detect some number of touches on the screen, but other than that I don't want the view to stop the behavior of other views underneath, which are scrollviews, etc. How can I forward all the touches through this overlay view? It is a subclass of UIView.
Disabling user interaction was all I needed!
Objective-C:
myWebView.userInteractionEnabled = NO;
Swift:
myWebView.isUserInteractionEnabled = false
For passing touches from an overlay view to the views underneath, implement the following method in the UIView:
Objective-C:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Passing all touches to the next view (if any), in the view stack.");
return NO;
}
Swift 5:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
print("Passing all touches to the next view (if any), in the view stack.")
return false
}
This is an old thread, but it came up on a search, so I thought I'd add my 2c. I have a covering UIView with subviews, and only want to intercept the touches that hit one of the subviews, so I modified PixelCloudSt's answer to:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
for (UIView* subview in self.subviews ) {
if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
return YES;
}
}
return NO;
}
Improved version of #fresidue answer. You can use this UIView subclass as transparent view passing touches outside its subview. Implementation in Objective-C:
#interface PassthroughView : UIView
#end
#implementation PassthroughView
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
for (UIView *view in self.subviews) {
if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
#end
.
and in Swift:
class PassthroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return subviews.contains(where: {
!$0.isHidden
&& $0.isUserInteractionEnabled
&& $0.point(inside: self.convert(point, to: $0), with: event)
})
}
}
TIP:
Say then you have a large "holder" panel, perhaps with a table view behind. You make the "holder" panel PassthroughView. It will now work, you can scroll the table "through" the "holder".
But!
On top of the "holder" panel you have some labels or icons. Don't forget, of course those must simply be marked user interaction enabled OFF!
On top of the "holder" panel you have some buttons. Don't forget, of course those must simply be marked user interaction enabled ON!
Note that somewhat confusingly, the "holder" itself - the view you use PassthroughView on - must be marked user interaction enabled ON! That's ON!! (Otherwise, the code in PassthroughView simply will never be called.)
I needed to pass touches through a UIStackView. A UIView inside was transparent, but the UIStackView consumed all touches. This worked for me:
class PassThrouStackView: UIStackView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if view == self {
return nil
}
return view
}
}
All arrangedSubviews still receive touches, but touches on the UIStackView itself went through to the view below (for me a mapView).
I had a similar issue with a UIStackView (but could be any other view).
My configuration was the following:
It's a classical case where I have a container that needed to be placed in the background, with buttons on the side. For layout purposes, I included the buttons in a UIStackView, but now the middle (empty) part of the stackView intercepts touches :-(
What I did is create a subclass of UIStackView with a property defining the subView that should be touchable.
Now, any touch on the side buttons (included in the * viewsWithActiveTouch* array) will be given to the buttons, while any touch on the stackview anywhere else than these views won't be intercepted, and therefore passed to whatever is below the stack view.
/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {
var viewsWithActiveTouch: [UIView]?
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if let activeViews = viewsWithActiveTouch {
for view in activeViews {
if CGRectContainsPoint(view.frame, point) {
return view
}
}
}
return nil
}
}
If the view you want to forward the touches to doesn't happen to be a subview / superview, you can set up a custom property in your UIView subclass like so:
#interface SomeViewSubclass : UIView {
id forwardableTouchee;
}
#property (retain) id forwardableTouchee;
Make sure to synthesize it in your .m:
#synthesize forwardableTouchee;
And then include the following in any of your UIResponder methods such as:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.forwardableTouchee touchesBegan:touches withEvent:event];
}
Wherever you instantiate your UIView, set the forwardableTouchee property to whatever view you'd like the events to be forwarded to:
SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
view.forwardableTouchee = someOtherView;
In Swift 5
class ThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let slideView = subviews.first else {
return false
}
return slideView.hitTest(convert(point, to: slideView), with: event) != nil
}
}
Looks like even thou its quite a lot of answers here, there is no one clean in swift that I needed.
So I took answer from #fresidue here and converted it to swift as it's what now mostly developers want to use here.
It solved my problem where I have some transparent toolbar with button but I want toolbar to be invisible to user and touch events should go through.
isUserInteractionEnabled = false as some stated is not an option based on my testing.
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.hitTest(convert(point, to: subview), with: event) != nil {
return true
}
}
return false
}
I had couple of labels inside StackView and I didn't have much success with the solutions above, instead I solved my problem using below code:
let item = ResponsiveLabel()
// Configure label
stackView.addArrangedSubview(item)
Subclassing UIStackView:
class PassThrouStackView:UIStackView{
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in self.arrangedSubviews {
let convertedPoint = convert(point, to: subview)
let labelPoint = subview.point(inside: convertedPoint, with: event)
if (labelPoint){
return subview
}
}
return nil
}
}
Then you could do something like:
class ResponsiveLabel:UILabel{
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Respond to touch
}
}
Try something like this...
for (UIView *view in subviews)
[view touchesBegan:touches withEvent:event];
The code above, in your touchesBegan method for example would pass the touches to all of the subviews of view.
The situation I was trying to do was build a control panel using controls inside nested UIStackView’s. Some of the controls had UITextField’s others with UIButton’s. Also, there were labels to identify the controls. What I wanted to do was put a big “invisible” button behind the control panel so that if a user tapped on an area outside a button or text field, that I could then catch that and take action - primarily dismiss any keyboard if a text field was active (resignFirstResponder). However, tapping on a label or other blank area in the control panel would not pass things through. The above discussions were helpful in coming up with my answer below.
Basically, I sub-classed UIStackView and overwrote the “point(inside:with) routine to look for the type of controls that needed the touch and “ignore” things like labels that I wanted to ignore. It also checks for inside UIStackView’s so that things can recurse into the control panel structure.
The code is a perhaps a little more verbose than it should be. But it was helpful in debugging and hopefully provides more clarity in what the routine is doing. Just be sure in Interface Builder to change the class of the UIStackView's to PassThruStack.
class PassThruStack: UIStackView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for view in self.subviews {
if !view.isHidden {
let isStack = view is UIStackView
let isButton = view is UIButton
let isText = view is UITextField
if isStack || isButton || isText {
let pointInside = view.point(inside: self.convert(point, to: view), with: event)
if pointInside {
return true
}
}
}
}
return false
}
}
As suggested by #PixelCloudStv if you want to throw touched from one view to another but with some additional control over this process - subclass UIView
//header
#interface TouchView : UIView
#property (assign, nonatomic) CGRect activeRect;
#end
//implementation
#import "TouchView.h"
#implementation TouchView
#pragma mark - Ovverride
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL moveTouch = YES;
if (CGRectContainsPoint(self.activeRect, point)) {
moveTouch = NO;
}
return moveTouch;
}
#end
After in interfaceBuilder just set class of View to TouchView and set active rect with your rect. Also u can change and implement other logic.

Resources