diff --git a/android/widgets/src/main/java/org/nativescript/widgets/DisableUserInteractionListener.java b/android/widgets/src/main/java/org/nativescript/widgets/DisableUserInteractionListener.java deleted file mode 100644 index e59aef48f..000000000 --- a/android/widgets/src/main/java/org/nativescript/widgets/DisableUserInteractionListener.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.nativescript.widgets; - -import android.view.MotionEvent; -import android.view.View; - -/** - * Created by hhristov on 2/22/17. - */ - -public final class DisableUserInteractionListener extends Object implements View.OnTouchListener { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - return true; - } -} diff --git a/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java index f011a4b48..ee9e3227c 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java @@ -6,6 +6,7 @@ package org.nativescript.widgets; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -20,6 +21,11 @@ public abstract class LayoutBase extends ViewGroup { super(context); } + private boolean passThroughParent; + + public boolean getPassThroughParent() { return this.passThroughParent; } + public void setPassThroughParent(boolean value) { this.passThroughParent = value; } + @Override protected LayoutParams generateDefaultLayoutParams() { return new CommonLayoutParams(); @@ -59,6 +65,18 @@ public abstract class LayoutBase extends ViewGroup { public boolean shouldDelayChildPressedState() { return false; } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!this.passThroughParent) { + return super.onTouchEvent(event); + } + + // LayoutBase.onTouchEvent(ev) execution means no interactive child view handled + // the event so we let the event pass through to parent view of the layout container + // because passThroughParent is set to true + return false; + } protected static int getGravity(View view) { int gravity = -1; diff --git a/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj b/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj index f6ccadaae..d98948fbe 100644 --- a/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj +++ b/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj @@ -9,6 +9,12 @@ /* Begin PBXBuildFile section */ 8B7321CF1D097ECD00884AC6 /* TNSLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B7321CD1D097ECD00884AC6 /* TNSLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8B7321D01D097ECD00884AC6 /* TNSLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7321CE1D097ECD00884AC6 /* TNSLabel.m */; }; + B8E76F52212C2DA2009CFCE2 /* NSObject+Swizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E76F50212C2DA2009CFCE2 /* NSObject+Swizzling.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B8E76F53212C2DA2009CFCE2 /* NSObject+Swizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = B8E76F51212C2DA2009CFCE2 /* NSObject+Swizzling.m */; }; + B8E76F5A212C2F4E009CFCE2 /* NSObject+PropertyBag.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E76F58212C2F4E009CFCE2 /* NSObject+PropertyBag.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B8E76F5B212C2F4E009CFCE2 /* NSObject+PropertyBag.m in Sources */ = {isa = PBXBuildFile; fileRef = B8E76F59212C2F4E009CFCE2 /* NSObject+PropertyBag.m */; }; + B8E76F5E212C3134009CFCE2 /* UIView+PassThroughParent.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B8E76F5F212C3134009CFCE2 /* UIView+PassThroughParent.m in Sources */ = {isa = PBXBuildFile; fileRef = B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */; }; F915D3551EC9EF5E00071914 /* TNSProcess.m in Sources */ = {isa = PBXBuildFile; fileRef = F915D3531EC9EF5E00071914 /* TNSProcess.m */; }; F915D3561EC9EF5E00071914 /* TNSProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = F915D3541EC9EF5E00071914 /* TNSProcess.h */; settings = {ATTRIBUTES = (Public, ); }; }; F98F5CB31CD0EFEA00978308 /* TNSWidgets.h in Headers */ = {isa = PBXBuildFile; fileRef = F98F5CB21CD0EFEA00978308 /* TNSWidgets.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -31,6 +37,12 @@ /* Begin PBXFileReference section */ 8B7321CD1D097ECD00884AC6 /* TNSLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TNSLabel.h; sourceTree = ""; }; 8B7321CE1D097ECD00884AC6 /* TNSLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TNSLabel.m; sourceTree = ""; }; + B8E76F50212C2DA2009CFCE2 /* NSObject+Swizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+Swizzling.h"; sourceTree = ""; }; + B8E76F51212C2DA2009CFCE2 /* NSObject+Swizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Swizzling.m"; sourceTree = ""; }; + B8E76F58212C2F4E009CFCE2 /* NSObject+PropertyBag.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+PropertyBag.h"; sourceTree = ""; }; + B8E76F59212C2F4E009CFCE2 /* NSObject+PropertyBag.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PropertyBag.m"; sourceTree = ""; }; + B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+PassThroughParent.h"; sourceTree = ""; }; + B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+PassThroughParent.m"; sourceTree = ""; }; F915D3531EC9EF5E00071914 /* TNSProcess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TNSProcess.m; path = ../TNSProcess.m; sourceTree = ""; }; F915D3541EC9EF5E00071914 /* TNSProcess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TNSProcess.h; path = ../TNSProcess.h; sourceTree = ""; }; F98F5CAF1CD0EFEA00978308 /* TNSWidgets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TNSWidgets.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -91,6 +103,12 @@ F98F5CC91CD0F09E00978308 /* UIImage+TNSBlocks.h */, F98F5CCA1CD0F09E00978308 /* UIImage+TNSBlocks.m */, F98F5CB41CD0EFEA00978308 /* Info.plist */, + B8E76F50212C2DA2009CFCE2 /* NSObject+Swizzling.h */, + B8E76F51212C2DA2009CFCE2 /* NSObject+Swizzling.m */, + B8E76F58212C2F4E009CFCE2 /* NSObject+PropertyBag.h */, + B8E76F59212C2F4E009CFCE2 /* NSObject+PropertyBag.m */, + B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */, + B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */, ); path = TNSWidgets; sourceTree = ""; @@ -113,7 +131,10 @@ files = ( F915D3561EC9EF5E00071914 /* TNSProcess.h in Headers */, F98F5CB31CD0EFEA00978308 /* TNSWidgets.h in Headers */, + B8E76F5E212C3134009CFCE2 /* UIView+PassThroughParent.h in Headers */, F98F5CCB1CD0F09E00978308 /* UIImage+TNSBlocks.h in Headers */, + B8E76F52212C2DA2009CFCE2 /* NSObject+Swizzling.h in Headers */, + B8E76F5A212C2F4E009CFCE2 /* NSObject+PropertyBag.h in Headers */, 8B7321CF1D097ECD00884AC6 /* TNSLabel.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -217,6 +238,9 @@ 8B7321D01D097ECD00884AC6 /* TNSLabel.m in Sources */, F915D3551EC9EF5E00071914 /* TNSProcess.m in Sources */, F98F5CCC1CD0F09E00978308 /* UIImage+TNSBlocks.m in Sources */, + B8E76F53212C2DA2009CFCE2 /* NSObject+Swizzling.m in Sources */, + B8E76F5B212C2F4E009CFCE2 /* NSObject+PropertyBag.m in Sources */, + B8E76F5F212C3134009CFCE2 /* UIView+PassThroughParent.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.h b/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.h new file mode 100644 index 000000000..d759a03a0 --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.h @@ -0,0 +1,17 @@ +// +// NSObject+PropertyBag.h +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import + + +@interface NSObject (PropertyBag) + +- (id) propertyValueForKey:(NSString*) key; +- (void) setPropertyValue:(id) value forKey:(NSString*) key; + +@end diff --git a/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.m b/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.m new file mode 100644 index 000000000..d4a232ca2 --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.m @@ -0,0 +1,62 @@ +// +// NSObject+PropertyBag.m +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import "NSObject+PropertyBag.h" +#import "NSObject+Swizzling.h" + + +@implementation NSObject (PropertyBag) + ++ (void) load{ + [self loadPropertyBag]; +} + ++ (void) loadPropertyBag{ + @autoreleasepool { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + const SEL deallocSelector = NSSelectorFromString(@"dealloc"); //ARC forbids use of 'dealloc' in a @selector + [self swizzleInstanceMethodWithOriginalSelector:deallocSelector fromClass:self.class withSwizzlingSelector:@selector(propertyBag_dealloc)]; + }); + } +} + +__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag +- (id) propertyValueForKey:(NSString*) key { + return [[self propertyBag] valueForKey:key]; +} + +- (void) setPropertyValue:(id) value forKey:(NSString*) key { + [[self propertyBag] setValue:value forKey:key]; +} + +- (NSMutableDictionary*) propertyBag { + if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; + NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p", self]]; + if (propBag == nil) { + propBag = [NSMutableDictionary dictionary]; + [self setPropertyBag:propBag]; + } + + return propBag; +} + +- (void) setPropertyBag:(NSDictionary*) propertyBag { + if (_propertyBagHolder == nil) { + _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; + } + + [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p", self]]; +} + +- (void)propertyBag_dealloc { + [self setPropertyBag:nil]; + [self propertyBag_dealloc]; // swizzled +} + +@end diff --git a/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.h b/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.h new file mode 100644 index 000000000..d3d71a88f --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.h @@ -0,0 +1,17 @@ +// +// NSObject+Swizzling.h +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import +#import + + +@interface NSObject (Swizzling) + ++ (void)swizzleInstanceMethodWithOriginalSelector:(SEL)originalSelector fromClass:(Class)classContainigOriginalSel withSwizzlingSelector:(SEL)swizzlingSelector; + +@end diff --git a/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.m b/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.m new file mode 100644 index 000000000..f9a65ac15 --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.m @@ -0,0 +1,68 @@ +// +// NSObject+Swizzling.m +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import "NSObject+Swizzling.h" + +@implementation NSObject (Swizzling) + +#pragma mark - Method Swizzling + ++ (void)swizzleInstanceMethodWithOriginalSelector:(SEL)originalSelector + fromClass:(Class)classContainigOriginalSel + withSwizzlingSelector:(SEL)swizzlingSelector { + Method originalMethod = class_getInstanceMethod(classContainigOriginalSel, originalSelector); + Method swizzlingMethod = class_getInstanceMethod(self.class, swizzlingSelector); + [self swizzleMethodWithOriginalSelector:originalSelector + originalMethod:originalMethod + fromClass:classContainigOriginalSel + withSwizzlingSelector:swizzlingSelector + swizzlingMethod:swizzlingMethod]; +} + +//MARK: Utilities + ++ (void)swizzleMethodWithOriginalSelector:(SEL)originalSelector + originalMethod:(Method)originalMethod + fromClass:(Class)classContainigOriginalSel + withSwizzlingSelector:(SEL)swizzlingSelector + swizzlingMethod:(Method)swizzlingMethod { + if (self == classContainigOriginalSel) { + BOOL didAddMethod = class_addMethod(classContainigOriginalSel, + originalSelector, + method_getImplementation(swizzlingMethod), + method_getTypeEncoding(swizzlingMethod)); + + if (didAddMethod) { + class_replaceMethod(self.class, + swizzlingSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzlingMethod); + } + + return; + } + + class_addMethod(classContainigOriginalSel, + swizzlingSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + + class_replaceMethod(classContainigOriginalSel, + originalSelector, + method_getImplementation(swizzlingMethod), + method_getTypeEncoding(swizzlingMethod)); + + class_replaceMethod(self, + swizzlingSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); +} + +@end diff --git a/ios/TNSWidgets/TNSWidgets/TNSWidgets.h b/ios/TNSWidgets/TNSWidgets/TNSWidgets.h index 28bae77c1..453d7cd6e 100644 --- a/ios/TNSWidgets/TNSWidgets/TNSWidgets.h +++ b/ios/TNSWidgets/TNSWidgets/TNSWidgets.h @@ -17,5 +17,6 @@ FOUNDATION_EXPORT const unsigned char TNSWidgetsVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import "UIImage+TNSBlocks.h" +#import "UIView+PassThroughParent.h" #import "TNSLabel.h" #import "TNSProcess.h" diff --git a/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.h b/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.h new file mode 100644 index 000000000..e5f68c568 --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.h @@ -0,0 +1,17 @@ +// +// UIView+PassThroughParent.h +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import + + +@interface UIView (PassThroughParent) + +- (BOOL) passThroughParent; +- (void) setPassThroughParent:(BOOL) passThroughParent; + +@end diff --git a/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.m b/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.m new file mode 100644 index 000000000..1afefd84e --- /dev/null +++ b/ios/TNSWidgets/TNSWidgets/UIView+PassthroughParent.m @@ -0,0 +1,53 @@ +// +// UIView+PassThroughParent.m +// TNSWidgets +// +// Created by Manol Donev on 21.08.18. +// Copyright © 2018 Telerik A D. All rights reserved. +// + +#import "UIView+PassThroughParent.h" +#import "NSObject+Swizzling.h" +#import "NSObject+PropertyBag.h" + + +NSString * const TLKPassThroughParentKey = @"passThroughParent"; + +@implementation UIView (PassThroughParent) + ++ (void) load { + [self loadHitTest]; +} + ++ (void) loadHitTest { + @autoreleasepool { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self swizzleInstanceMethodWithOriginalSelector:@selector(hitTest:withEvent:) fromClass:self.class withSwizzlingSelector:@selector(passThrough_hitTest:withEvent:)]; + }); + } +} + +- (BOOL)passThroughParent { + NSNumber *passthrough = [self propertyValueForKey:TLKPassThroughParentKey]; + if (passthrough) { + return passthrough.boolValue; + }; + + return NO; +} + +- (void)setPassThroughParent:(BOOL)passThroughParent { + [self setPropertyValue:[NSNumber numberWithBool:passThroughParent] forKey:TLKPassThroughParentKey]; +} + +- (UIView *)passThrough_hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *hitTestView = [self passThrough_hitTest:point withEvent:event]; // swizzled + if (hitTestView == self && self.passThroughParent) { + hitTestView = nil; + } + + return hitTestView; +} + +@end