Skip to content

Commit

Permalink
Release 4.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
zhujg-00 committed Nov 18, 2021
1 parent 20aec30 commit 218f7cd
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 85 deletions.
2 changes: 1 addition & 1 deletion SensorsAnalyticsSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SensorsAnalyticsSDK"
s.version = "4.0.1"
s.version = "4.0.2"
s.summary = "The official iOS SDK of Sensors Analytics."
s.homepage = "http://www.sensorsdata.cn"
s.source = { :git => 'https://github.com/sensorsdata/sa-sdk-ios.git', :tag => "v#{s.version}" }
Expand Down
25 changes: 0 additions & 25 deletions SensorsAnalyticsSDK/Core/Builder/EventObject/SATrackEventObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
#import "SAValidator.h"
#import "SALog.h"

static NSSet *presetEventNames;

@implementation SATrackEventObject

- (instancetype)initWithEventId:(NSString *)eventId {
Expand Down Expand Up @@ -147,29 +145,6 @@ - (void)addChannelProperties:(NSDictionary *)properties {
[self.properties addEntriesFromDictionary:properties];
}

- (void)validateEventWithError:(NSError **)error {
[super validateEventWithError:error];
if (*error) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
presetEventNames = [NSSet setWithObjects:
kSAEventNameAppStart,
kSAEventNameAppStartPassively ,
kSAEventNameAppEnd,
kSAEventNameAppViewScreen,
kSAEventNameAppClick,
kSAEventNameSignUp,
kSAEventNameAppCrashed,
kSAEventNameAppRemoteConfigChanged, nil];
});
//事件校验,预置事件提醒
if ([presetEventNames containsObject:self.eventId]) {
SALogWarn(@"\n【event warning】\n %@ is a preset event name of us, it is recommended that you use a new one", self.eventId);
}
}

@end

@implementation SAAutoTrackEventObject
Expand Down
2 changes: 1 addition & 1 deletion SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
#import "SAJSONUtil.h"
#import "SAApplication.h"

#define VERSION @"4.0.1"
#define VERSION @"4.0.2"

void *SensorsAnalyticsQueueTag = &SensorsAnalyticsQueueTag;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,6 @@
/// 一个 view 上子视图可见区域
@property (nonatomic, assign, readonly) CGRect sensorsdata_visibleFrame;

/// 是否禁用 RCTView 子视图交互
@property (nonatomic, assign) BOOL sensorsdata_isDisableRNSubviewsInteractive;
@end
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@

typedef BOOL (*SAClickableImplementation)(id, SEL, UIView *);


static void *const kSAIsDisableRNSubviewsInteractivePropertyName = (void *)&kSAIsDisableRNSubviewsInteractivePropertyName;

#pragma mark - UIView
@implementation UIView (SAElementPath)

Expand Down Expand Up @@ -126,6 +129,11 @@ - (BOOL)sensorsdata_isAutoTrackAppClick {
return NO;
}

// RN 已禁用了子视图交互
if (![SAVisualizedUtils isInteractiveEnabledRNView:self]) {
return NO;
}

// UISegmentedControl 嵌套 UISegment 作为选项单元格,特殊处理
if ([NSStringFromClass(self.class) isEqualToString:@"UISegment"]) {
UISegmentedControl *segmentedControl = (UISegmentedControl *)[self superview];
Expand Down Expand Up @@ -268,7 +276,12 @@ - (NSArray *)sensorsdata_subElements {
}

NSMutableArray *newSubViews = [NSMutableArray array];
for (UIView *view in self.subviews) {
NSArray<UIView *>* subViews = self.subviews;
// 针对 RCTView,获取按照 zIndex 排序后的子元素
if ([SAVisualizedUtils isKindOfRCTView:self]) {
subViews = [SAVisualizedUtils sortedRNSubviewsWithView:self];
}
for (UIView *view in subViews) {
if (view.sensorsdata_isVisible) {
[newSubViews addObject:view];
}
Expand Down Expand Up @@ -389,6 +402,14 @@ - (CGRect)sensorsdata_visibleFrame {
return visibleFrame;
}

- (BOOL)sensorsdata_isDisableRNSubviewsInteractive {
return [objc_getAssociatedObject(self, kSAIsDisableRNSubviewsInteractivePropertyName) boolValue];
}

- (void)setSensorsdata_isDisableRNSubviewsInteractive:(BOOL)sensorsdata_isDisableRNSubviewsInteractive {
objc_setAssociatedObject(self, kSAIsDisableRNSubviewsInteractivePropertyName, @(sensorsdata_isDisableRNSubviewsInteractive), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end


Expand Down
24 changes: 18 additions & 6 deletions SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ NS_ASSUME_NONNULL_BEGIN
/// 解析构造 web 元素
+ (NSArray *)analysisWebElementWithWebView:(WKWebView *)webView;

/// 获取 RN 当前页面信息
+ (NSDictionary <NSString *, NSString *>*)currentRNScreenVisualizeProperties;

/// 是否为 RN 内的原生页面
+ (BOOL)isRNCustomViewController:(UIViewController *)viewController;

/// 获取当前有效的 keyWindow
+ (UIWindow *)currentValidKeyWindow;

Expand All @@ -60,6 +54,24 @@ NS_ASSUME_NONNULL_BEGIN
/// 是否支持打通,包含新老打通
/// @param webview 需要判断的 webview
+ (BOOL)isSupportCallJSWithWebView:(WKWebView *)webview;

#pragma mark - RN
/// 获取 RN 当前页面信息
+ (NSDictionary <NSString *, NSString *>*)currentRNScreenVisualizeProperties;

/// 是否为 RN 内的原生页面
+ (BOOL)isRNCustomViewController:(UIViewController *)viewController;

/// 是否为RCTView 类型
+ (BOOL)isKindOfRCTView:(UIView *)view;

/// 获取 RCTView 按照 zIndex 排序后的子元素
/// @param view 当前元素
+ (NSArray<UIView *> *)sortedRNSubviewsWithView:(UIView *)view;

/// 是否为可交互的 RN 元素
/// @param view 需要判断的 RN 元素
+ (BOOL)isInteractiveEnabledRNView:(UIView *)view;
@end

#pragma mark -
Expand Down
179 changes: 128 additions & 51 deletions SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@

/// 遍历查找页面最大层数,用于判断元素是否被覆盖
static NSInteger kSAVisualizedFindMaxPageLevel = 4;
typedef NSArray<UIView *>* (*SASortedRNSubviewsMethod)(UIView *, SEL);

/// RCTView 响应交互类型
typedef NS_ENUM(NSInteger, SARCTViewPointerEvents) {
/// 0: 默认类型,优先使用子视图响应交互
SARCTViewPointerEventsUnspecified = 0,
/// 1: 自身以及子视图都不响应交互,所以不阻塞下层 view 交互
SARCTViewPointerEventsNone,
/// 2: 只让子视图响应交互,自身不可点击
SARCTViewPointerEventsBoxNone,
/// 3: 只有自身接收事件,子视图不可交互
SARCTViewPointerEventsBoxOnly,
};

@implementation SAVisualizedUtils

Expand All @@ -62,31 +75,28 @@ + (BOOL)isCoveredForView:(UIView *)view {
/// @param view 被遮挡的 RNView
/// @param fromView 遮挡的 RNView
+ (BOOL)isCoveredOfRNView:(UIView *)view fromRNView:(UIView *)fromView {
@try {
/* RCTView 默认重写了 hitTest:
详情参照:https://github.com/facebook/react-native/blob/master/React/Views/RCTView.m
针对 RN 部分框架或实现方式,设置 pointerEvents 并在 hitTest: 内判断处理,从而实现交互的穿透,不响应当前 RNView
*/
NSInteger pointerEvents = [[fromView valueForKey:@"pointerEvents"] integerValue];
// RCTView 重写 hitTest: 并返回 nil,不阻塞底下元素交互
if (pointerEvents == 1) {
return NO;
}
// 遍历子视图判断是否存在坐标覆盖阻塞交互
if (pointerEvents == 2) {
// 寻找完全遮挡 view 的子视图
for (UIView *subView in fromView.subviews) {
BOOL enableInteraction = [SAVisualizedUtils isVisibleForView:subView] && subView.userInteractionEnabled;
BOOL isCovered = [self isCoveredForView:view fromView:subView];
if (enableInteraction && isCovered) {
return YES;
}
/* RCTView 默认重写了 hitTest:
详情参照:https://github.com/facebook/react-native/blob/master/React/Views/RCTView.m
针对 RN 部分框架或实现方式,设置 pointerEvents 并在 hitTest: 内判断处理,从而实现交互的穿透,不响应当前 RNView
*/
SARCTViewPointerEvents pointerEvents = [self pointEventsWithRCTView:fromView];
// RCTView 重写 hitTest: 并返回 nil,不阻塞底下元素交互
if (pointerEvents == SARCTViewPointerEventsNone) {
return NO;
}
// 遍历子视图判断是否存在坐标覆盖阻塞交互
if (pointerEvents == SARCTViewPointerEventsBoxNone) {
// 寻找完全遮挡 view 的子视图
for (UIView *subView in fromView.subviews) {
BOOL enableInteraction = [SAVisualizedUtils isVisibleForView:subView] && subView.userInteractionEnabled;
BOOL isCovered = [self isCoveredForView:view fromView:subView];
if (enableInteraction && isCovered) {
return YES;
}
return NO;
}
} @catch (NSException *exception) {
SALogDebug(@"%@ error: %@", self, exception);
return NO;
}

return [self isCoveredForView:view fromView:fromView];
}

Expand Down Expand Up @@ -123,9 +133,22 @@ + (BOOL)isCoveredForView:(UIView *)view fromView:(UIView *)fromView {
+ (NSArray *)findPossibleCoverAllBrotherViews:(UIView *)view {
NSMutableArray <UIView *> *otherViews = [NSMutableArray array];
UIView *superView = [view superview];
if (superView) {
NSArray *subviews = superView.subviews;

if ([self isKindOfRCTView:superView]) {
/* RCTView 默认重写了 hitTest:
如果 pointerEvents = 0 或 2,会优先从按照 reactZIndex 排序后的数组 reactZIndexSortedSubviews 中逆序遍历查询用于交互的 view。所以这里,针对 pointerEvents = 0 或 2,也需要从 reactZIndexSortedSubviews 获取父试图的子视图,用于判断同级元素的交互遮挡。
详情参照:https://github.com/facebook/react-native/blob/master/React/Views/RCTView.m
*/
SARCTViewPointerEvents pointerEvents = [self pointEventsWithRCTView:superView];
// RCTView 重写 hitTest: 并返回 nil,不阻塞底下元素交互
if (pointerEvents != SARCTViewPointerEventsNone && pointerEvents != SARCTViewPointerEventsBoxOnly) {
subviews = [self sortedRNSubviewsWithView:superView];
}
}
if (subviews) {
// 逆序遍历
[superView.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
[subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj == view) {
*stop = YES;
} else if ([self isVisibleForView:obj] && obj.userInteractionEnabled) { // userInteractionEnabled 为 YES 才有可能遮挡响应事件
Expand All @@ -141,11 +164,6 @@ + (BOOL)isVisibleForView:(UIView *)view {
return view.alpha > 0.01 && !view.isHidden;
}

+ (BOOL)isKindOfRCTView:(UIView *)view {
Class RCTView = NSClassFromString(@"RCTView");
return RCTView && [view isKindOfClass:RCTView];
}

#pragma mark WebElement
+ (NSArray *)analysisWebElementWithWebView:(WKWebView <SAVisualizedExtensionProperty> *)webView {
SAVisualizedWebPageInfo *webPageInfo = [[SAVisualizedObjectSerializerManager sharedInstance] readWebPageInfoWithWebView:webView];
Expand Down Expand Up @@ -204,6 +222,13 @@ + (NSArray *)analysisWebElementWithWebView:(WKWebView <SAVisualizedExtensionProp
}

#pragma mark RNUtils

// 是否为RCTView 类型
+ (BOOL)isKindOfRCTView:(UIView *)view {
Class rctViewClass = NSClassFromString(@"RCTView");
return rctViewClass && [view isKindOfClass:rctViewClass];
}

+ (NSDictionary *)currentRNScreenVisualizeProperties {
// 获取 RN 页面信息
NSDictionary <NSString *, NSString *> *RNScreenInfo = nil;
Expand Down Expand Up @@ -241,29 +266,63 @@ + (BOOL)isRNCustomViewController:(UIViewController *)viewController {
return NO;
}

#pragma mark keyWindow
/// 获取当前有效的 keyWindow
+ (UIWindow *)currentValidKeyWindow {
UIWindow *keyWindow = [self currentKeyWindow];
// 判断 keyWindow 是否显示
if ([self isVisibleForView:keyWindow]) {
return keyWindow;
// 获取 RCTView 按照 zIndex 排序后的子元素
+ (NSArray<UIView *> *)sortedRNSubviewsWithView:(UIView *)view {
SEL sortedRNSubviewsSel = NSSelectorFromString(@"reactZIndexSortedSubviews");
if (![view respondsToSelector:sortedRNSubviewsSel]) {
return view.subviews;
}
SASortedRNSubviewsMethod method = (SASortedRNSubviewsMethod)[view methodForSelector:sortedRNSubviewsSel];
return method(view, sortedRNSubviewsSel);
}

__block UIWindow *validWindow = nil;
// 逆序遍历,获取最上层全屏 window
[[UIApplication sharedApplication].windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIWindow * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGSize fullScreenSize = [UIScreen mainScreen].bounds.size;
if ([obj isMemberOfClass:UIWindow.class] && CGSizeEqualToSize(fullScreenSize, obj.frame.size) && [self isVisibleForView:obj]) {
validWindow = obj;
*stop = YES;
}
}];
return validWindow;
+ (BOOL)isInteractiveEnabledRNView:(UIView *)view {
/* RCTView 默认重写了 hitTest:,对应做兼容处理
详情参照:https://github.com/facebook/react-native/blob/master/React/Views/RCTView.m
*/
// 当前 view 的父视图是否禁用子视图交互
if (view.superview.sensorsdata_isDisableRNSubviewsInteractive) {
view.sensorsdata_isDisableRNSubviewsInteractive = YES;
return NO;
}

if (![self isKindOfRCTView:view]) {
return YES;
}

// 设置交互状态
SARCTViewPointerEvents pointerEvents = [self pointEventsWithRCTView:view];
// None 和 BoxOnly 都禁用子视图交互
BOOL isEventsNone = pointerEvents == SARCTViewPointerEventsNone;
BOOL isEventsBoxOnly = pointerEvents == SARCTViewPointerEventsBoxOnly;
view.sensorsdata_isDisableRNSubviewsInteractive = isEventsNone || isEventsBoxOnly;

// EventsNone 和 EventsBoxNone 时,自身不可交互
if (pointerEvents == SARCTViewPointerEventsNone || pointerEvents == SARCTViewPointerEventsBoxNone) {
return NO;
}

return YES;
}

/// 获取当前 RCTView 的交互类型
+ (SARCTViewPointerEvents)pointEventsWithRCTView:(UIView *)view {
if (![self isKindOfRCTView:view]) {
return SARCTViewPointerEventsUnspecified;
}

SARCTViewPointerEvents pointerEvents = SARCTViewPointerEventsUnspecified;
@try {
pointerEvents = [[view valueForKey:@"pointerEvents"] integerValue];
} @catch (NSException *exception) {
SALogWarn(@"%@ error: %@", self, exception);
}
return pointerEvents;
}

// 获取当前 keyWindow
+ (UIWindow *)currentKeyWindow {
#pragma mark keyWindow
/// 获取当前有效的 keyWindow
+ (UIWindow *)currentValidKeyWindow {
UIWindow *keyWindow = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
Expand All @@ -276,8 +335,7 @@ + (UIWindow *)currentKeyWindow {
}
// iOS 13 及以上,可能动态设置其他 window 为 keyWindow,此时直接使用此 keyWindow
if (window.isKeyWindow) {
keyWindow = window;
break;
return window;
}
// 获取 windowScene.windows 中第一个 window
if (!keyWindow) {
Expand All @@ -289,7 +347,26 @@ + (UIWindow *)currentKeyWindow {
}
}
#endif
return keyWindow ?: [UIApplication sharedApplication].keyWindow;
return keyWindow ?: [self topWindow];
}

+ (UIWindow *)topWindow {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
NSArray<UIWindow *> *allWindows = [UIApplication sharedApplication].windows;

// 如果 windows 未包含 keyWindow,可能是 iOS13 以下系统弹出 UIAlertView 等场景,此时忽略 UIAlertView 所在 window
if ([allWindows containsObject:keyWindow]) {
return keyWindow;
}

// 逆序遍历,获取最上层全屏可见 window
CGSize fullScreenSize = [UIScreen mainScreen].bounds.size;
for (UIWindow *window in [allWindows reverseObjectEnumerator]) {
if ([window isMemberOfClass:UIWindow.class] && CGSizeEqualToSize(fullScreenSize, window.frame.size) && [self isVisibleForView:window]) {
return window;
}
}
return nil;
}

#pragma mark viewTree
Expand Down

0 comments on commit 218f7cd

Please sign in to comment.