-mouseMoved OSX does not get called in a sprite kit SKScene - macos

The following responder (defined in NSResponder) does not get called in an SKScene in OSX:
-(void) mouseMoved:(NSEvent *)theEvent {
DLog(#"TEST");
}
I have said the window to accept mouse moved events in the app delegate.
_window.acceptsMouseMovedEvents = YES;
Thank you in advance.
SOLUTION:
Add to the app delegate:
_window.acceptsMouseMovedEvents = YES;
[_window makeFirstResponder:self.skView.scene];

Add to the app delegate:
_window.acceptsMouseMovedEvents = YES;
[_window makeFirstResponder:self.skView.scene];

In swift
window.acceptsMouseMovedEvents = true;
window.makeFirstResponder(self.skView.scene)

You can get at the window object and set it from the scene.
in Swift:
override func willMove(from view: SKView)
{
self.view!.window?.acceptsMouseMovedEvents = true
}

The above answers either make too many assumptions about what’s outside of your SKScene, or add a touch of unnecessary hackiness by messing with the responder chain.
SKView is a subclass of UIView. It inherits, therefore, the viewDidMoveToWindow: method. You can activate mouse-moved events for all your SKViews with a simple extension.
extension SKView {
open override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
window?.acceptsMouseMovedEvents = true
}
}

Related

Call Action when NSStatusBarButton is right-clicked

I am searching for a way to detect whenever the NSStatusBarButton is right-clicked (using Swift) and call an action.
I am currently setting it up this way:
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "myImage")
button.alternateImage = NSImage(named: "myImage")
button.action = Selector("myAction")
}
}
I thought of using the button.rightMouseDown(<#theEvent: NSEvent#>) (Because there is no such "alternateAction") but unfortunately I did not manage to come up with something due to the fact that I just started programming Mac apps.
Update:
While searching for a way to do this I saw some threads telling to subclass a NSView but I don't se how this should work (This could be because I am really new to programming and don't even know how to "subclass"). Still I thought there was some easier way to use this since nearly every statusBar App that I know rects on right-clicks.
You can subclass and override the mouseDown method, but since Mac OS X 10.10 (Yosemite), there has been an easier way: NSGestureRecognizer and its subclasses:
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "myImage")
button.alternateImage = NSImage(named: "myImage")
button.action = Selector("myAction")
// Add right click functionality
let gesture = NSClickGestureRecognizer()
gesture.buttonMask = 0x2 // right mouse
gesture.target = self
gesture.action = "myRightClickAction:"
button.addGestureRecognizer(gesture)
}
}
func myRightClickAction(sender: NSGestureRecognizer) {
if let button = sender.view as? NSButton {
// Handle your right click event here
}
}
I had the same problem as you with the accepted answer's method: it didn't work for buttonMask 0x2, only buttonMask 0x1. Regular NSButtons (but not NSStatusBarButtons) can handle NSClickGestureRecognizers, so perhaps that's what the answerer was thinking. Another solution I found suggested was to set the NSStatusItem's view to an instance of your own custom subclass of NSView, but as of OS X v10.10, getting or setting view is deprecated, so I didn't want to do that.
I solved this by adding a custom subclass of NSView as a subview of the NSStatusItem's button. My NSView implements -rightMouseUp: to receive the right mouse up event and then just passes that event to a block given to it by my class that wants to handle the right mouse click event.
Here's my custom subclass:
#import <Cocoa/Cocoa.h>
#interface TTRightClickDetector : NSView
#property (copy) void (^onRightMouseClicked)(NSEvent *);
#end
#import "TTRightClickDetector.h"
And the implementation:
#implementation TTRightClickDetector
- (void)rightMouseUp:(NSEvent *)theEvent
{
if(self.onRightMouseClicked)
{
self.onRightMouseClicked(theEvent);
}
}
#end
And here's how I use it:
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSStatusBarButton *button = self.statusItem.button;
button.image = [NSImage imageNamed:#"image"];
button.action = #selector(leftMouseClicked:);
TTRightClickDetector *rightClickDetector = [[TTRightClickDetector alloc] initWithFrame:button.frame];
rightClickDetector.onRightMouseClicked = ^(NSEvent *event){
[self rightMouseClicked];
};
[button addSubview:rightClickDetector];
The swift version of commanda's answer (subclassing an NSView and implementing mouseDown).
NSView Subclass:
class RightMouseHandlerView: NSView {
var onRightMouseDown: (()->())? = nil
override func rightMouseDown(with event: NSEvent) {
super.rightMouseDown(with: event)
if onRightMouseDown != nil {
onRightMouseDown!()
}
}
}
Then adding it to the status bar button and setting the code block:
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
if let button = statusItem.button {
let rmhView = RightMouseHandlerView(frame: statusItem.button!.frame)
rmhView.rightMouseDown = {
// Do something when right mouse down on button
}
button.addSubview(rmView)
}

Detecting when a button is touched in SKScene?

I created a storyboard and added a scene with two buttons. I cannot figure out how to know when a button is pressed on my GameScene.swift class.
How can this be done?
You can you touchesBegan for that.
Here is example code for you:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches{
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.playButton{
//your code
}
}
}
You appear to be mixing UIKit and SpriteKit here. I would personally advise against using UIButtons in conjunction with Sprite Kit. Is there a specific reason for doing so?
There are two ways you can implement button behavior within a Sprite Kit scene:
have the SKScene object handle the touches
have the button itself handle the touches
Dharmesh's answer uses method (1), where he implements the -touchesBegan method.
In my current project, I am using an SKNode subclass as a button (2). I am unfamiliar with Swift syntax so I have posted Objective-C code from my project instead. The method calls are similar though and should help illustrate the point.
If you want an SKNode to receive touches, set userInteractionEnabled to YES. Otherwise, the closest ancestor with userInteractionEnabled = YES (which typically is the containing SKScene) will receive a -touchesBegan/-touchesMoved/-touchesEnded message.
#interface VTObject : SKNode
#end
...
#implementation VTObject
- (instancetype)init {
if (self = [super init]) {
self.userInteractionEnabled = YES;
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"button touched!");
}
#end
You should add the UIButton programatically, instead of in IB, to the SKScene's SKView (in didMoveToView for example). You can then set the target for the button with button.addTarget:action:forControlEvents:. Just remember to call button.removeFromSuperview() in willMoveFromView otherwise you'll see the buttons in your next scene.

iOS 8 presentationController determine if really is popover

I'm using the new adaptive "Present As Popover" capability of iOS 8. I wired up a simple segue in the StoryBoard to do the presentation. It works great on an iPhone 6 Plus as it presents the view as a popover and on an iPhone 4s it shows as a full screen view (sheet style).
The problem is when shown as a full screen view, I need to add a "Done" button to the view so dismissViewControllerAnimated can be called. And I don't want to show the "done" button when it's shown as a popover.
I tried looking at the properties of both presentationController and popoverPresentationController, and I can find nothing that tells me if it is actually being shown as a popover.
NSLog( #"View loaded %lx", (long)self.presentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.presentationController.presentationStyle ); // UIModalPresentationPopover
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.presentationStyle ); // UIModalPresentationPopover
adaptivePresentationStyle always returns UIModalPresentationFullScreen and presentationStyle always returns UIModalPresentationPopover
When looking at the UITraitCollection I did find a trait called "_UITraitNameInteractionModel" which was only set to 1 when it was actually displayed as a Popover. However, Apple doesn't provide direct access to that trait through the traitCollection of popoverPresentationController.
The best way (least smelly) I've found to do this is to use the UIPopoverPresentationControllerDelegate.
• Ensure the presented view controller is set as the UIPopoverPresentationControllerDelegate on the UIPopoverPresentationController being used to manage the presentation. I'm using a Storyboard so set this in prepareForSegue:
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
• Create a property in the presented view controller to keep track of this state:
#property (nonatomic, assign) BOOL amDisplayedInAPopover;
• And add the following delegate method (or add to your existing delegate method):
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
self.amDisplayedInAPopover = YES;
}
• And then finally in viewWillAppear: - viewDidLoad: is too early, the delegate prepare method is called between viewDidLoad: and viewWillAppear:
if (self.amDisplayedInAPopover) {
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
Edit: Simpler method!
Just set the delegate (making sure your presentedVC adopts the UIPopoverPresentationControllerDelegate):
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
And supply the method:
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
I check to see if the popoverPresentationController's arrowDirection is set after the view is laid out. For my purposes, this works well enough and covers the case of popovers on smaller screened devices.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (popoverPresentationController?.arrowDirection != UIPopoverArrowDirection.Unknown) {
// This view controller is running in a popover
NSLog("I'm running in a Popover")
}
}
How about
if (self.modalPresentationStyle == UIModalPresentationPopover)
It's working for me
The official way to implement this is first remove the Done button from your view controller and second, when adapting to compact embed your view controller in a navigation controller, adding the done button as a navigation item:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.FullScreen
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
return navigationController
}
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
Full Tutorial
I tested all solutions presented in this post. Sorry, none works correctly in all cases. For example in iPad split view presentation style can change while dragging split view line, so we need specific notification for that.
After few hours of researches i found solution in apple sample (swift):
https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636
Here is the same solution in obj-c.
First in prepareForSegue function set the popoverPresentationController delegate. It can be also set in MyViewController "init", but not in "viewDidLoad" (because first willPresentWithAdaptiveStyle is called before viewDidLoad).
MyViewController *controller = [segue destinationViewController];
controller.popoverPresentationController.delegate = (MyViewController *)controller;
Now MyViewController object will receive this notification every time iOS changes presentation style, including first presenting. Here is example implementation which shows/hides "Close" button in navigationController:
- (void)presentationController:(UIPresentationController *)presentationController
willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style
transitionCoordinator:(nullable id<UIViewControllerTransitionCoordinator>)transitionCoordinator {
if (style == UIModalPresentationNone) {
// style set in storyboard not changed (popover), hide close button
self.topViewController.navigationItem.leftBarButtonItem = nil;
} else {
// style changed by iOS (to fullscreen or page sheet), show close button
UIBarButtonItem *closeButton =
[[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeAction)];
self.topViewController.navigationItem.leftBarButtonItem = closeButton;
}
}
- (void)closeAction {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
The UIPresentationController which manages your view controller is presenting it by setting the modalPresentationStyle to UIModalPresentationPopover.
As per UIViewController reference:
presentingViewController
The view controller that presented this view
controller. (read-only)
modalPresentationStyle
UIModalPresentationPopover: In a horizontally regular environment, a presentation style where the content is displayed in a popover view. The background content is dimmed and taps
outside the popover cause the popover to be dismissed. If you do not
want taps to dismiss the popover, you can assign one or more views to
the passthroughViews property of the associated
UIPopoverPresentationController object, which you can get from the
popoverPresentationController property.
We can therefore determine whether your view controller is inside a popover or presented modally by checking the horizontalSizeClass as follows (I assumed your button is a UIBarButtonItem)
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
self.navigationItem.leftBarButtonItem = nil; // remove the button
}
The safest place to check this is in viewWillAppear: as otherwise the presentingViewController may be nil.
Solution that works with multitasking
Assign the presenting controller as the popover's delegate
...
controller.popoverPresentationController.delegate = controller;
[self presentViewController:controller animated:YES completion:nil];
Then, in the controller, implement the delegate methods:
- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(id<UIViewControllerTransitionCoordinator>)transitionCoordinator
{
if (style != UIModalPresentationNone)
{
// Exited popover mode
self.navigationItem.leftBarButtonItem = button;
}
}
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// Entered popover mode
self.navigationItem.leftBarButtonItem = nil;
}
My tricky solution, works perfectly.
In the PopoverViewController's viewDidLoad.
if (self.view.superview!.bounds != UIScreen.main.bounds) {
print("This is a popover!")
}
The idea is simple, A Popover's view size is never equal to the device screen size unless it's not a Popover.

iOS 8 - UIPopoverPresentationController moving popover

I am looking for an effective way to re-position a popover using the new uipopoverpresentationcontroller. I have succesfully presented the popover, and now I want to move it without dismissing and presenting again. I am having trouble using the function:
(void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view
I know it's early in the game, but it anyone has an example of how to do this efficiently I would be grateful if you shared it with me. Thanks in advance.
Unfortunately this hacky workaround is the only solution I've found:
[vc.popoverPresentationController setSourceRect:newSourceRect];
[vc setPreferredContentSize:CGRectInset(vc.view.frame, -0.01, 0.0).size];
This temporarily changes the content size of the presented view, causing the popover and arrow to be repositioned. The temporary change in size is not visible.
It seems this is a problem Apple need to fix - changing the sourceView or sourceRect properties of UIPopoverPresentationController does nothing when it's already presenting a popover (without this workaround).
Hope this works for you too!
I had luck using containerView?.setNeedsLayout() and containerView?.layoutIfNeeded() after changing the sourceRect of the popoverPresentationController, like so:
func movePopoverTo(_ newRect: CGRect) {
let popover = self.presentedViewController as? MyPopoverViewController {
popover.popoverPresentationController?.sourceRect = newRect
popover.popoverPresentationController?.containerView?.setNeedsLayout()
popover.popoverPresentationController?.containerView?.layoutIfNeeded()
}
}
And even to have a popover follow a tableView cell without having to change anything:
class MyTableViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MyPopoverSegue" {
guard let controller = segue.destination as? MyPopoverViewController else { fatalError("Expected destination controller to be a 'MyPopoverViewController'!") }
guard let popoverPresentationController = controller.popoverPresentationController else { fatalError("No popoverPresentationController!") }
guard let rowIndexPath = sender as? IndexPath else { fatalError("Expected sender to be an 'IndexPath'!") }
guard myData.count > rowIndexPath.row else { fatalError("Index (\(rowIndexPath.row)) Out Of Bounds for array (count: \(myData.count))!") }
if self.presentedViewController is MyPopoverViewController {
self.presentedViewController?.dismiss(animated: false)
}
popoverPresentationController.sourceView = self.tableView
popoverPresentationController.sourceRect = self.tableView.rectForRow(at: rowIndexPath)
popoverPresentationController.passthroughViews = [self.tableView]
controller.configure(myData[rowIndexPath.row])
}
super.prepare(for: segue, sender: sender)
}
}
// MARK: - UIScrollViewDelegate
extension MyTableViewController {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let popover = self.presentedViewController as? MyPopoverViewController {
popover.popoverPresentationController?.containerView?.setNeedsLayout()
popover.popoverPresentationController?.containerView?.layoutIfNeeded()
}
}
}
I used the same method as mentioned in another answer by #Rowan_Jones, however I didn't want the popover's size to actually change. Even by fractions of a point. I realized that you can set the preferredContentSize multiple times back to back, but visually it's size will only change to match the last value.
[vc.popoverPresentationController setSourceRect:newSourceRect];
CGSize finalDesiredSize = CGSizeMake(320, 480);
CGSize tempSize = CGSizeMake(finalDesiredSize.width, finalDesiredSize.height + 1);
[vc setPreferredContentSize:tempSize];
[vc setPreferredContentSize:finalDesiredSize];
So even if finalDesiredSize is the same as your initial preferredContentSize this will cause the popover to be updated, even though it's size doesn't actually change.
Here is an example for how to recenter the popover:
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view {
*rect = CGRectMake((CGRectGetWidth((*view).bounds)-2)*0.5f,(CGRectGetHeight((*view).bounds)-2)*0.5f, 2, 2);
I have also used this method to ensure that the popover moved to the correct location after moving by setting the *rect and the *view to the original sourceRect and sourceView.
As an additional note, I don't believe that this method is called when the popover's source is set using a bar button item.
I'm posting this because I don't have enough points to vote or comment. :)
#turbs's answer worked for me perfectly. It should be the accepted answer.
Setting *rect to the rect you need in the delegate method:
(void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view
iOS 12.3
[vc.popoverPresentationController setSourceRect:newSourceRect];
[vc.popoverPresentationController.containerView setNeedsLayout];

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