diff --git a/SensorsAnalyticsSDK.podspec b/SensorsAnalyticsSDK.podspec index 8eeb20d5..79269634 100644 --- a/SensorsAnalyticsSDK.podspec +++ b/SensorsAnalyticsSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SensorsAnalyticsSDK" - s.version = "4.4.5" + s.version = "4.4.6" 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}" } @@ -36,8 +36,8 @@ Pod::Spec.new do |s| c.dependency 'SensorsAnalyticsSDK/Extension' c.public_header_files = 'SensorsAnalyticsSDK/JSBridge/SensorsAnalyticsSDK+JavaScriptBridge.h' c.source_files = 'SensorsAnalyticsSDK/Core/SAAlertController.{h,m}', 'SensorsAnalyticsSDK/JSBridge/**/*.{h,m}' - c.ios.source_files = 'SensorsAnalyticsSDK/RemoteConfig/**/*.{h,m}', 'SensorsAnalyticsSDK/ChannelMatch/**/*.{h,m}', 'SensorsAnalyticsSDK/Encrypt/**/*.{h,m}', 'SensorsAnalyticsSDK/Deeplink/**/*.{h,m}', 'SensorsAnalyticsSDK/DebugMode/**/*.{h,m}', 'SensorsAnalyticsSDK/Core/SAAlertController.h' - c.ios.public_header_files = 'SensorsAnalyticsSDK/{Encrypt,RemoteConfig,ChannelMatch,Deeplink,DebugMode}/{SAConfigOptions,SensorsAnalyticsSDK}+*.h', 'SensorsAnalyticsSDK/Encrypt/SAEncryptProtocol.h', 'SensorsAnalyticsSDK/Encrypt/SASecretKey.h', 'SensorsAnalyticsSDK/Deeplink/SASlinkCreator.h' + c.ios.source_files = 'SensorsAnalyticsSDK/RemoteConfig/**/*.{h,m}', 'SensorsAnalyticsSDK/ChannelMatch/**/*.{h,m}', 'SensorsAnalyticsSDK/Encrypt/**/*.{h,m}', 'SensorsAnalyticsSDK/Deeplink/**/*.{h,m}', 'SensorsAnalyticsSDK/DebugMode/**/*.{h,m}', 'SensorsAnalyticsSDK/Core/SAAlertController.h', 'SensorsAnalyticsSDK/UIRelated/**/*.{h,m}' + c.ios.public_header_files = 'SensorsAnalyticsSDK/{Encrypt,RemoteConfig,ChannelMatch,Deeplink,DebugMode}/{SAConfigOptions,SensorsAnalyticsSDK}+*.h', 'SensorsAnalyticsSDK/Encrypt/SAEncryptProtocol.h', 'SensorsAnalyticsSDK/Encrypt/SASecretKey.h', 'SensorsAnalyticsSDK/Deeplink/SASlinkCreator.h', 'SensorsAnalyticsSDK/UIRelated/UIView+SensorsAnalytics.h' end s.subspec 'Core' do |c| @@ -125,4 +125,11 @@ Pod::Spec.new do |s| d.project_header_files = 'CellClick_HookDelegate_Deprecated/*.h' end + s.subspec 'Exposure' do |h| + h.ios.deployment_target = '8.0' + h.dependency 'SensorsAnalyticsSDK/Common' + h.source_files = 'SensorsAnalyticsSDK/Exposure/**/*.{h,m}' + h.public_header_files = 'SensorsAnalyticsSDK/Exposure/SAConfigOptions+Exposure.h', 'SensorsAnalyticsSDK/Exposure/SAExposureConfig.h', 'SensorsAnalyticsSDK/Exposure/SAExposureData.h', 'SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.h', 'SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.h' + end + end diff --git a/SensorsAnalyticsSDK.xcodeproj/project.pbxproj b/SensorsAnalyticsSDK.xcodeproj/project.pbxproj index 1dd7b222..4066d6f0 100644 --- a/SensorsAnalyticsSDK.xcodeproj/project.pbxproj +++ b/SensorsAnalyticsSDK.xcodeproj/project.pbxproj @@ -119,9 +119,9 @@ 4DA89BC425C2BC1E003ABA43 /* SAReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DA89BC025C2BC1E003ABA43 /* SAReachability.m */; }; 4DAFDCE8282622810074A691 /* SACustomPropertyPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DAFDCE6282622810074A691 /* SACustomPropertyPlugin.h */; }; 4DAFDCE9282622810074A691 /* SACustomPropertyPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DAFDCE7282622810074A691 /* SACustomPropertyPlugin.m */; }; - 4DD1282025F87225008C0B1E /* UIView+SAElementPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD1281D25F87225008C0B1E /* UIView+SAElementPath.h */; }; + 4DD1282025F87225008C0B1E /* UIView+SAVisualizedViewPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD1281D25F87225008C0B1E /* UIView+SAVisualizedViewPath.h */; }; 4DD1282125F87225008C0B1E /* SAVisualizedViewPathProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD1281E25F87225008C0B1E /* SAVisualizedViewPathProperty.h */; }; - 4DD1282225F87225008C0B1E /* UIView+SAElementPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD1281F25F87225008C0B1E /* UIView+SAElementPath.m */; }; + 4DD1282225F87225008C0B1E /* UIView+SAVisualizedViewPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD1281F25F87225008C0B1E /* UIView+SAVisualizedViewPath.m */; }; 4DD1284B25F872A4008C0B1E /* SAWebElementView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD1282625F872A2008C0B1E /* SAWebElementView.h */; }; 4DD1284C25F872A4008C0B1E /* NSInvocation+SAHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD1282725F872A2008C0B1E /* NSInvocation+SAHelpers.m */; }; 4DD1284D25F872A4008C0B1E /* SAVisualizedConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD1282825F872A2008C0B1E /* SAVisualizedConnection.m */; }; @@ -406,10 +406,42 @@ CB6EBAF322855222003CFBA8 /* cert.outdate.cer in Resources */ = {isa = PBXBuildFile; fileRef = CB6EBAEF22855222003CFBA8 /* cert.outdate.cer */; }; CB6EBAF422855222003CFBA8 /* ca.cer1 in Resources */ = {isa = PBXBuildFile; fileRef = CB6EBAF022855222003CFBA8 /* ca.cer1 */; }; CB6EBAF522855222003CFBA8 /* ca.der.cer in Resources */ = {isa = PBXBuildFile; fileRef = CB6EBAF122855222003CFBA8 /* ca.der.cer */; }; + F200094F28BDADB5003C5113 /* UITableViewCell+SAIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F200094D28BDADB5003C5113 /* UITableViewCell+SAIndexPath.h */; }; + F200095028BDADB5003C5113 /* UITableViewCell+SAIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F200094E28BDADB5003C5113 /* UITableViewCell+SAIndexPath.m */; }; + F200095328BDE7DE003C5113 /* UIAlertController+SASimilarPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F200095128BDE7DE003C5113 /* UIAlertController+SASimilarPath.h */; }; + F200095428BDE7DE003C5113 /* UIAlertController+SASimilarPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F200095228BDE7DE003C5113 /* UIAlertController+SASimilarPath.m */; }; + F200095628BE0363003C5113 /* SAUIInternalProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F200095528BE0363003C5113 /* SAUIInternalProperties.h */; }; + F200095928BE067A003C5113 /* UIView+SAElementID.h in Headers */ = {isa = PBXBuildFile; fileRef = F200095728BE067A003C5113 /* UIView+SAElementID.h */; }; + F200095A28BE067A003C5113 /* UIView+SAElementID.m in Sources */ = {isa = PBXBuildFile; fileRef = F200095828BE067A003C5113 /* UIView+SAElementID.m */; }; + F200095D28BE0721003C5113 /* UIView+SAInternalProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F200095B28BE0721003C5113 /* UIView+SAInternalProperties.h */; }; + F200095E28BE0721003C5113 /* UIView+SAInternalProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = F200095C28BE0721003C5113 /* UIView+SAInternalProperties.m */; }; + F200096128BE10B5003C5113 /* UIViewController+SAInternalProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F200095F28BE10B5003C5113 /* UIViewController+SAInternalProperties.h */; }; + F200096228BE10B5003C5113 /* UIViewController+SAInternalProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = F200096028BE10B5003C5113 /* UIViewController+SAInternalProperties.m */; }; + F20231CA28BF43FB0034D8B3 /* UIView+SARNView.h in Headers */ = {isa = PBXBuildFile; fileRef = F20231C828BF43FB0034D8B3 /* UIView+SARNView.h */; }; + F20231CB28BF43FB0034D8B3 /* UIView+SARNView.m in Sources */ = {isa = PBXBuildFile; fileRef = F20231C928BF43FB0034D8B3 /* UIView+SARNView.m */; }; + F20231D028C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.h in Headers */ = {isa = PBXBuildFile; fileRef = F20231CE28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.h */; }; + F20231D128C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.m in Sources */ = {isa = PBXBuildFile; fileRef = F20231CF28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.m */; }; F2066972271FFEE10002ABDF /* UIViewController+SAPageLeave.m in Sources */ = {isa = PBXBuildFile; fileRef = F2066970271FFEE10002ABDF /* UIViewController+SAPageLeave.m */; }; F2066973271FFEE10002ABDF /* UIViewController+SAPageLeave.h in Headers */ = {isa = PBXBuildFile; fileRef = F2066971271FFEE10002ABDF /* UIViewController+SAPageLeave.h */; }; + F209BEB728B360A6000CEE49 /* UIView+ExposureIdentifier.h in Headers */ = {isa = PBXBuildFile; fileRef = F209BEB528B360A6000CEE49 /* UIView+ExposureIdentifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F209BEB828B360A6000CEE49 /* UIView+ExposureIdentifier.m in Sources */ = {isa = PBXBuildFile; fileRef = F209BEB628B360A6000CEE49 /* UIView+ExposureIdentifier.m */; }; F211742126E9A72C00D65E19 /* SAApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = F211741F26E9A72B00D65E19 /* SAApplication.h */; }; F211742226E9A72C00D65E19 /* SAApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = F211742026E9A72B00D65E19 /* SAApplication.m */; }; + F226E66C28BC6149000443A7 /* SAUIProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E66A28BC6149000443A7 /* SAUIProperties.h */; }; + F226E66D28BC6149000443A7 /* SAUIProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E66B28BC6149000443A7 /* SAUIProperties.m */; }; + F226E67028BC62EB000443A7 /* UIView+SAItemPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E66E28BC62EB000443A7 /* UIView+SAItemPath.h */; }; + F226E67128BC62EB000443A7 /* UIView+SAItemPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E66F28BC62EB000443A7 /* UIView+SAItemPath.m */; }; + F226E67428BC631B000443A7 /* UIView+SASimilarPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E67228BC631B000443A7 /* UIView+SASimilarPath.h */; }; + F226E67528BC631B000443A7 /* UIView+SASimilarPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E67328BC631B000443A7 /* UIView+SASimilarPath.m */; }; + F226E67828BC63F5000443A7 /* UIView+SAElementType.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E67628BC63F5000443A7 /* UIView+SAElementType.h */; }; + F226E67928BC63F5000443A7 /* UIView+SAElementType.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E67728BC63F5000443A7 /* UIView+SAElementType.m */; }; + F226E67C28BC6415000443A7 /* UIView+SAElementContent.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E67A28BC6415000443A7 /* UIView+SAElementContent.h */; }; + F226E67D28BC6415000443A7 /* UIView+SAElementContent.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E67B28BC6415000443A7 /* UIView+SAElementContent.m */; }; + F226E68028BC6454000443A7 /* UIView+SAElementPosition.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E67E28BC6454000443A7 /* UIView+SAElementPosition.h */; }; + F226E68128BC6454000443A7 /* UIView+SAElementPosition.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E67F28BC6454000443A7 /* UIView+SAElementPosition.m */; }; + F226E68428BC646F000443A7 /* UIView+SAElementPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E68228BC646F000443A7 /* UIView+SAElementPath.h */; }; + F226E68528BC646F000443A7 /* UIView+SAElementPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F226E68328BC646F000443A7 /* UIView+SAElementPath.m */; }; + F226E68C28BC993C000443A7 /* SAUIViewElementProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F226E68A28BC993C000443A7 /* SAUIViewElementProperties.h */; }; F22E1B1B26A55C8A0033A748 /* SAAppPageLeaveTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = F22E1B1926A55C8A0033A748 /* SAAppPageLeaveTracker.h */; }; F22E1B1C26A55C8A0033A748 /* SAAppPageLeaveTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = F22E1B1A26A55C8A0033A748 /* SAAppPageLeaveTracker.m */; }; F23CA0052701715E002EEACA /* WKWebView+SABridge.h in Headers */ = {isa = PBXBuildFile; fileRef = F23C9FFE2701715E002EEACA /* WKWebView+SABridge.h */; }; @@ -417,8 +449,15 @@ F23CA0082701715E002EEACA /* SensorsAnalyticsSDK+JavaScriptBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = F23CA0012701715E002EEACA /* SensorsAnalyticsSDK+JavaScriptBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; F23CA0092701715E002EEACA /* WKWebView+SABridge.m in Sources */ = {isa = PBXBuildFile; fileRef = F23CA0022701715E002EEACA /* WKWebView+SABridge.m */; }; F23CA00A2701715E002EEACA /* SAJavaScriptBridgeManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F23CA0032701715E002EEACA /* SAJavaScriptBridgeManager.h */; }; + F26A23CB28BCADD800AB84A6 /* UIView+SensorsAnalytics.h in Headers */ = {isa = PBXBuildFile; fileRef = F26A23C928BCADD800AB84A6 /* UIView+SensorsAnalytics.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F26A23CC28BCADD800AB84A6 /* UIView+SensorsAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = F26A23CA28BCADD800AB84A6 /* UIView+SensorsAnalytics.m */; }; + F26A23CE28BCD18100AB84A6 /* SAUIViewPathProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F26A23CD28BCD18100AB84A6 /* SAUIViewPathProperties.h */; }; F26FDDD8270312C400E1DF32 /* SAConfigOptions+AppPush.h in Headers */ = {isa = PBXBuildFile; fileRef = F26FDDD7270312C300E1DF32 /* SAConfigOptions+AppPush.h */; settings = {ATTRIBUTES = (Public, ); }; }; F26FDDDA2703130700E1DF32 /* SAConfigOptions+Exception.h in Headers */ = {isa = PBXBuildFile; fileRef = F26FDDD92703130700E1DF32 /* SAConfigOptions+Exception.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F273487528A9E92C00C34E64 /* UIScrollView+ExposureListener.h in Headers */ = {isa = PBXBuildFile; fileRef = F273487328A9E92C00C34E64 /* UIScrollView+ExposureListener.h */; }; + F273487628A9E92C00C34E64 /* UIScrollView+ExposureListener.m in Sources */ = {isa = PBXBuildFile; fileRef = F273487428A9E92C00C34E64 /* UIScrollView+ExposureListener.m */; }; + F273487928A9EA1500C34E64 /* SAExposureDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = F273487728A9EA1500C34E64 /* SAExposureDelegateProxy.h */; }; + F273487A28A9EA1500C34E64 /* SAExposureDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = F273487828A9EA1500C34E64 /* SAExposureDelegateProxy.m */; }; F277F5BF25CF9A43009B5CE6 /* SAApplicationDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = F277F5B125CF9A43009B5CE6 /* SAApplicationDelegateProxy.m */; }; F277F5C025CF9A43009B5CE6 /* SAUNUserNotificationCenterDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = F277F5B225CF9A43009B5CE6 /* SAUNUserNotificationCenterDelegateProxy.h */; }; F277F5C125CF9A43009B5CE6 /* SANotificationUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = F277F5B325CF9A43009B5CE6 /* SANotificationUtil.m */; }; @@ -440,15 +479,20 @@ F27EA3CE2739068C00896B3A /* SAEventTrackerPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F27EA3CC2739068C00896B3A /* SAEventTrackerPluginManager.m */; }; F27EA3D627393B4B00896B3A /* SACellClickDynamicSubclassPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = F27EA3D427393B4B00896B3A /* SACellClickDynamicSubclassPlugin.h */; }; F27EA3D727393B4B00896B3A /* SACellClickDynamicSubclassPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = F27EA3D527393B4B00896B3A /* SACellClickDynamicSubclassPlugin.m */; }; + F286963228A34FFC00276F78 /* SAExposureManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F286963028A34FFC00276F78 /* SAExposureManager.h */; }; + F286963328A34FFC00276F78 /* SAExposureManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F286963128A34FFC00276F78 /* SAExposureManager.m */; }; F28997D7273B6D66005E7D5E /* SAGesturePlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = F28997D5273B6D66005E7D5E /* SAGesturePlugin.h */; }; F28997D8273B6D66005E7D5E /* SAGesturePlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = F28997D6273B6D66005E7D5E /* SAGesturePlugin.m */; }; F2B643F62832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = F2B643F42832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; F2B643F72832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = F2B643F52832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.m */; }; + F2C877B828A65849002BDA2C /* SAExposureData+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = F2C877B628A65849002BDA2C /* SAExposureData+Private.h */; }; + F2CD8A7A28A2410A00A186B8 /* SAExposureConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = F2CD8A7828A2410A00A186B8 /* SAExposureConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2CD8A7B28A2410A00A186B8 /* SAExposureConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = F2CD8A7928A2410A00A186B8 /* SAExposureConfig.m */; }; F2CFD14726EB04A8007A9253 /* SAConfigOptions+RemoteConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = F2CFD14526EB04A8007A9253 /* SAConfigOptions+RemoteConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F2E364872876EE94008D9151 /* SASlinkCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E364852876EE94008D9151 /* SASlinkCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F2E364882876EE94008D9151 /* SASlinkCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E364862876EE94008D9151 /* SASlinkCreator.m */; }; F2E36483287682E6008D9151 /* SADeviceWhiteList.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E36481287682E6008D9151 /* SADeviceWhiteList.h */; }; F2E36484287682E6008D9151 /* SADeviceWhiteList.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E36482287682E6008D9151 /* SADeviceWhiteList.m */; }; + F2E364872876EE94008D9151 /* SASlinkCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E364852876EE94008D9151 /* SASlinkCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2E364882876EE94008D9151 /* SASlinkCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E364862876EE94008D9151 /* SASlinkCreator.m */; }; F2E4AB9D26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E4AB9B26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.h */; settings = {ATTRIBUTES = (Public, ); }; }; F2E4AB9E26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E4AB9C26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.m */; }; F2E4ABA126ECAA8600BA7F01 /* SensorsAnalyticsSDK+DeviceOrientation.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E4AB9F26ECAA8600BA7F01 /* SensorsAnalyticsSDK+DeviceOrientation.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -459,6 +503,20 @@ F2E4ABAA26ECB19200BA7F01 /* SensorsAnalyticsSDK+DebugMode.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E4ABA826ECB19200BA7F01 /* SensorsAnalyticsSDK+DebugMode.m */; }; F2E9723125E637820009A2B9 /* SAAppPushManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F2E9722F25E637820009A2B9 /* SAAppPushManager.h */; }; F2E9723225E637820009A2B9 /* SAAppPushManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F2E9723025E637820009A2B9 /* SAAppPushManager.m */; }; + F2FBB33528A25835008D10EB /* SAConfigOptions+Exposure.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBB33328A25835008D10EB /* SAConfigOptions+Exposure.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2FBB33928A2692D008D10EB /* SAExposureData.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBB33728A2692D008D10EB /* SAExposureData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2FBB33A28A2692D008D10EB /* SAExposureData.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBB33828A2692D008D10EB /* SAExposureData.m */; }; + F2FBB33D28A26B87008D10EB /* SAExposureConfig+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBB33B28A26B87008D10EB /* SAExposureConfig+Private.h */; }; + F2FBB34128A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBB33F28A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2FBB34228A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBB34028A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.m */; }; + F2FBBBAE28A39D2200F75293 /* UIView+ExposureListener.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBBBAC28A39D2200F75293 /* UIView+ExposureListener.h */; }; + F2FBBBAF28A39D2200F75293 /* UIView+ExposureListener.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBBBAD28A39D2200F75293 /* UIView+ExposureListener.m */; }; + F2FBBBB228A3A33300F75293 /* UIViewController+ExposureListener.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBBBB028A3A33300F75293 /* UIViewController+ExposureListener.h */; }; + F2FBBBB328A3A33300F75293 /* UIViewController+ExposureListener.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBBBB128A3A33300F75293 /* UIViewController+ExposureListener.m */; }; + F2FBBBB628A3A77000F75293 /* SAExposureViewObject.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBBBB428A3A77000F75293 /* SAExposureViewObject.h */; }; + F2FBBBB728A3A77000F75293 /* SAExposureViewObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBBBB528A3A77000F75293 /* SAExposureViewObject.m */; }; + F2FBBBBA28A3AAD300F75293 /* SAExposureTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FBBBB828A3AAD300F75293 /* SAExposureTimer.h */; }; + F2FBBBBB28A3AAD300F75293 /* SAExposureTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FBBBB928A3AAD300F75293 /* SAExposureTimer.m */; }; FC002920262C189E00A18FE3 /* SAConfigOptions+Encrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = FC0028EF2629756400A18FE3 /* SAConfigOptions+Encrypt.h */; settings = {ATTRIBUTES = (Public, ); }; }; FC0A8C63276334F200109267 /* SADeepLinkConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = FC0A8C61276334F200109267 /* SADeepLinkConstants.h */; }; FC0A8C64276334F200109267 /* SADeepLinkConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = FC0A8C62276334F200109267 /* SADeepLinkConstants.m */; }; @@ -621,9 +679,9 @@ 4DA89BC025C2BC1E003ABA43 /* SAReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAReachability.m; sourceTree = ""; }; 4DAFDCE6282622810074A691 /* SACustomPropertyPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SACustomPropertyPlugin.h; sourceTree = ""; }; 4DAFDCE7282622810074A691 /* SACustomPropertyPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SACustomPropertyPlugin.m; sourceTree = ""; }; - 4DD1281D25F87225008C0B1E /* UIView+SAElementPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementPath.h"; sourceTree = ""; }; + 4DD1281D25F87225008C0B1E /* UIView+SAVisualizedViewPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+SAVisualizedViewPath.h"; sourceTree = ""; }; 4DD1281E25F87225008C0B1E /* SAVisualizedViewPathProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAVisualizedViewPathProperty.h; sourceTree = ""; }; - 4DD1281F25F87225008C0B1E /* UIView+SAElementPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementPath.m"; sourceTree = ""; }; + 4DD1281F25F87225008C0B1E /* UIView+SAVisualizedViewPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAVisualizedViewPath.m"; sourceTree = ""; }; 4DD1282625F872A2008C0B1E /* SAWebElementView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAWebElementView.h; sourceTree = ""; }; 4DD1282725F872A2008C0B1E /* NSInvocation+SAHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSInvocation+SAHelpers.m"; sourceTree = ""; }; 4DD1282825F872A2008C0B1E /* SAVisualizedConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAVisualizedConnection.m; sourceTree = ""; }; @@ -905,11 +963,43 @@ CB6EBAEF22855222003CFBA8 /* cert.outdate.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = cert.outdate.cer; sourceTree = ""; }; CB6EBAF022855222003CFBA8 /* ca.cer1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ca.cer1; sourceTree = ""; }; CB6EBAF122855222003CFBA8 /* ca.der.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = ca.der.cer; sourceTree = ""; }; + F200094D28BDADB5003C5113 /* UITableViewCell+SAIndexPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UITableViewCell+SAIndexPath.h"; sourceTree = ""; }; + F200094E28BDADB5003C5113 /* UITableViewCell+SAIndexPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UITableViewCell+SAIndexPath.m"; sourceTree = ""; }; + F200095128BDE7DE003C5113 /* UIAlertController+SASimilarPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIAlertController+SASimilarPath.h"; sourceTree = ""; }; + F200095228BDE7DE003C5113 /* UIAlertController+SASimilarPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIAlertController+SASimilarPath.m"; sourceTree = ""; }; + F200095528BE0363003C5113 /* SAUIInternalProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAUIInternalProperties.h; sourceTree = ""; }; + F200095728BE067A003C5113 /* UIView+SAElementID.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementID.h"; sourceTree = ""; }; + F200095828BE067A003C5113 /* UIView+SAElementID.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementID.m"; sourceTree = ""; }; + F200095B28BE0721003C5113 /* UIView+SAInternalProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAInternalProperties.h"; sourceTree = ""; }; + F200095C28BE0721003C5113 /* UIView+SAInternalProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAInternalProperties.m"; sourceTree = ""; }; + F200095F28BE10B5003C5113 /* UIViewController+SAInternalProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SAInternalProperties.h"; sourceTree = ""; }; + F200096028BE10B5003C5113 /* UIViewController+SAInternalProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SAInternalProperties.m"; sourceTree = ""; }; + F20231C828BF43FB0034D8B3 /* UIView+SARNView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SARNView.h"; sourceTree = ""; }; + F20231C928BF43FB0034D8B3 /* UIView+SARNView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SARNView.m"; sourceTree = ""; }; + F20231CE28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SADelegateHashTable.h"; sourceTree = ""; }; + F20231CF28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SADelegateHashTable.m"; sourceTree = ""; }; F2066970271FFEE10002ABDF /* UIViewController+SAPageLeave.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SAPageLeave.m"; sourceTree = ""; }; F2066971271FFEE10002ABDF /* UIViewController+SAPageLeave.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SAPageLeave.h"; sourceTree = ""; }; + F209BEB528B360A6000CEE49 /* UIView+ExposureIdentifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+ExposureIdentifier.h"; sourceTree = ""; }; + F209BEB628B360A6000CEE49 /* UIView+ExposureIdentifier.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+ExposureIdentifier.m"; sourceTree = ""; }; F211741F26E9A72B00D65E19 /* SAApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAApplication.h; sourceTree = ""; }; F211742026E9A72B00D65E19 /* SAApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAApplication.m; sourceTree = ""; }; F214CE57249A07DF00A2633D /* SADatabaseUnitTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SADatabaseUnitTest.m; path = SensorsAnalyticsTests/Tracker/SADatabaseUnitTest.m; sourceTree = SOURCE_ROOT; }; + F226E66A28BC6149000443A7 /* SAUIProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAUIProperties.h; sourceTree = ""; }; + F226E66B28BC6149000443A7 /* SAUIProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAUIProperties.m; sourceTree = ""; }; + F226E66E28BC62EB000443A7 /* UIView+SAItemPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAItemPath.h"; sourceTree = ""; }; + F226E66F28BC62EB000443A7 /* UIView+SAItemPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAItemPath.m"; sourceTree = ""; }; + F226E67228BC631B000443A7 /* UIView+SASimilarPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SASimilarPath.h"; sourceTree = ""; }; + F226E67328BC631B000443A7 /* UIView+SASimilarPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SASimilarPath.m"; sourceTree = ""; }; + F226E67628BC63F5000443A7 /* UIView+SAElementType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementType.h"; sourceTree = ""; }; + F226E67728BC63F5000443A7 /* UIView+SAElementType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementType.m"; sourceTree = ""; }; + F226E67A28BC6415000443A7 /* UIView+SAElementContent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementContent.h"; sourceTree = ""; }; + F226E67B28BC6415000443A7 /* UIView+SAElementContent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementContent.m"; sourceTree = ""; }; + F226E67E28BC6454000443A7 /* UIView+SAElementPosition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementPosition.h"; sourceTree = ""; }; + F226E67F28BC6454000443A7 /* UIView+SAElementPosition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementPosition.m"; sourceTree = ""; }; + F226E68228BC646F000443A7 /* UIView+SAElementPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SAElementPath.h"; sourceTree = ""; }; + F226E68328BC646F000443A7 /* UIView+SAElementPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SAElementPath.m"; sourceTree = ""; }; + F226E68A28BC993C000443A7 /* SAUIViewElementProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAUIViewElementProperties.h; sourceTree = ""; }; F22E1B1926A55C8A0033A748 /* SAAppPageLeaveTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAAppPageLeaveTracker.h; sourceTree = ""; }; F22E1B1A26A55C8A0033A748 /* SAAppPageLeaveTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAAppPageLeaveTracker.m; sourceTree = ""; }; F23C9FFE2701715E002EEACA /* WKWebView+SABridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "WKWebView+SABridge.h"; sourceTree = ""; }; @@ -917,8 +1007,15 @@ F23CA0012701715E002EEACA /* SensorsAnalyticsSDK+JavaScriptBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SensorsAnalyticsSDK+JavaScriptBridge.h"; sourceTree = ""; }; F23CA0022701715E002EEACA /* WKWebView+SABridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "WKWebView+SABridge.m"; sourceTree = ""; }; F23CA0032701715E002EEACA /* SAJavaScriptBridgeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAJavaScriptBridgeManager.h; sourceTree = ""; }; + F26A23C928BCADD800AB84A6 /* UIView+SensorsAnalytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SensorsAnalytics.h"; sourceTree = ""; }; + F26A23CA28BCADD800AB84A6 /* UIView+SensorsAnalytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SensorsAnalytics.m"; sourceTree = ""; }; + F26A23CD28BCD18100AB84A6 /* SAUIViewPathProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAUIViewPathProperties.h; sourceTree = ""; }; F26FDDD7270312C300E1DF32 /* SAConfigOptions+AppPush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SAConfigOptions+AppPush.h"; sourceTree = ""; }; F26FDDD92703130700E1DF32 /* SAConfigOptions+Exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SAConfigOptions+Exception.h"; sourceTree = ""; }; + F273487328A9E92C00C34E64 /* UIScrollView+ExposureListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+ExposureListener.h"; sourceTree = ""; }; + F273487428A9E92C00C34E64 /* UIScrollView+ExposureListener.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+ExposureListener.m"; sourceTree = ""; }; + F273487728A9EA1500C34E64 /* SAExposureDelegateProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureDelegateProxy.h; sourceTree = ""; }; + F273487828A9EA1500C34E64 /* SAExposureDelegateProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureDelegateProxy.m; sourceTree = ""; }; F277F5B125CF9A43009B5CE6 /* SAApplicationDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAApplicationDelegateProxy.m; sourceTree = ""; }; F277F5B225CF9A43009B5CE6 /* SAUNUserNotificationCenterDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAUNUserNotificationCenterDelegateProxy.h; sourceTree = ""; }; F277F5B325CF9A43009B5CE6 /* SANotificationUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SANotificationUtil.m; sourceTree = ""; }; @@ -940,15 +1037,20 @@ F27EA3CC2739068C00896B3A /* SAEventTrackerPluginManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAEventTrackerPluginManager.m; sourceTree = ""; }; F27EA3D427393B4B00896B3A /* SACellClickDynamicSubclassPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SACellClickDynamicSubclassPlugin.h; sourceTree = ""; }; F27EA3D527393B4B00896B3A /* SACellClickDynamicSubclassPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SACellClickDynamicSubclassPlugin.m; sourceTree = ""; }; + F286963028A34FFC00276F78 /* SAExposureManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureManager.h; sourceTree = ""; }; + F286963128A34FFC00276F78 /* SAExposureManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureManager.m; sourceTree = ""; }; F28997D5273B6D66005E7D5E /* SAGesturePlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAGesturePlugin.h; sourceTree = ""; }; F28997D6273B6D66005E7D5E /* SAGesturePlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAGesturePlugin.m; sourceTree = ""; }; F2B643F42832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SensorsAnalyticsSDK+SAAppExtension.h"; sourceTree = ""; }; F2B643F52832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SensorsAnalyticsSDK+SAAppExtension.m"; sourceTree = ""; }; + F2C877B628A65849002BDA2C /* SAExposureData+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SAExposureData+Private.h"; sourceTree = ""; }; + F2CD8A7828A2410A00A186B8 /* SAExposureConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureConfig.h; sourceTree = ""; }; + F2CD8A7928A2410A00A186B8 /* SAExposureConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureConfig.m; sourceTree = ""; }; F2CFD14526EB04A8007A9253 /* SAConfigOptions+RemoteConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SAConfigOptions+RemoteConfig.h"; sourceTree = ""; }; - F2E364852876EE94008D9151 /* SASlinkCreator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SASlinkCreator.h; sourceTree = ""; }; - F2E364862876EE94008D9151 /* SASlinkCreator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SASlinkCreator.m; sourceTree = ""; }; F2E36481287682E6008D9151 /* SADeviceWhiteList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SADeviceWhiteList.h; sourceTree = ""; }; F2E36482287682E6008D9151 /* SADeviceWhiteList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SADeviceWhiteList.m; sourceTree = ""; }; + F2E364852876EE94008D9151 /* SASlinkCreator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SASlinkCreator.h; sourceTree = ""; }; + F2E364862876EE94008D9151 /* SASlinkCreator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SASlinkCreator.m; sourceTree = ""; }; F2E4AB9B26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SensorsAnalyticsSDK+Location.h"; sourceTree = ""; }; F2E4AB9C26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SensorsAnalyticsSDK+Location.m"; sourceTree = ""; }; F2E4AB9F26ECAA8600BA7F01 /* SensorsAnalyticsSDK+DeviceOrientation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SensorsAnalyticsSDK+DeviceOrientation.h"; sourceTree = ""; }; @@ -959,6 +1061,20 @@ F2E4ABA826ECB19200BA7F01 /* SensorsAnalyticsSDK+DebugMode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SensorsAnalyticsSDK+DebugMode.m"; sourceTree = ""; }; F2E9722F25E637820009A2B9 /* SAAppPushManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAAppPushManager.h; sourceTree = ""; }; F2E9723025E637820009A2B9 /* SAAppPushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAAppPushManager.m; sourceTree = ""; }; + F2FBB33328A25835008D10EB /* SAConfigOptions+Exposure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SAConfigOptions+Exposure.h"; sourceTree = ""; }; + F2FBB33728A2692D008D10EB /* SAExposureData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureData.h; sourceTree = ""; }; + F2FBB33828A2692D008D10EB /* SAExposureData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureData.m; sourceTree = ""; }; + F2FBB33B28A26B87008D10EB /* SAExposureConfig+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SAExposureConfig+Private.h"; sourceTree = ""; }; + F2FBB33F28A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SensorsAnalyticsSDK+Exposure.h"; sourceTree = ""; }; + F2FBB34028A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SensorsAnalyticsSDK+Exposure.m"; sourceTree = ""; }; + F2FBBBAC28A39D2200F75293 /* UIView+ExposureListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+ExposureListener.h"; sourceTree = ""; }; + F2FBBBAD28A39D2200F75293 /* UIView+ExposureListener.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+ExposureListener.m"; sourceTree = ""; }; + F2FBBBB028A3A33300F75293 /* UIViewController+ExposureListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+ExposureListener.h"; sourceTree = ""; }; + F2FBBBB128A3A33300F75293 /* UIViewController+ExposureListener.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+ExposureListener.m"; sourceTree = ""; }; + F2FBBBB428A3A77000F75293 /* SAExposureViewObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureViewObject.h; sourceTree = ""; }; + F2FBBBB528A3A77000F75293 /* SAExposureViewObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureViewObject.m; sourceTree = ""; }; + F2FBBBB828A3AAD300F75293 /* SAExposureTimer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAExposureTimer.h; sourceTree = ""; }; + F2FBBBB928A3AAD300F75293 /* SAExposureTimer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAExposureTimer.m; sourceTree = ""; }; FC0028EF2629756400A18FE3 /* SAConfigOptions+Encrypt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SAConfigOptions+Encrypt.h"; sourceTree = ""; }; FC0A8C61276334F200109267 /* SADeepLinkConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SADeepLinkConstants.h; sourceTree = ""; }; FC0A8C62276334F200109267 /* SADeepLinkConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SADeepLinkConstants.m; sourceTree = ""; }; @@ -1090,8 +1206,8 @@ isa = PBXGroup; children = ( 4DD1281E25F87225008C0B1E /* SAVisualizedViewPathProperty.h */, - 4DD1281D25F87225008C0B1E /* UIView+SAElementPath.h */, - 4DD1281F25F87225008C0B1E /* UIView+SAElementPath.m */, + 4DD1281D25F87225008C0B1E /* UIView+SAVisualizedViewPath.h */, + 4DD1281F25F87225008C0B1E /* UIView+SAVisualizedViewPath.m */, 4D41D9D125FF7E9300D856F4 /* UIViewController+SAElementPath.h */, 4D41D9D225FF7E9300D856F4 /* UIViewController+SAElementPath.m */, ); @@ -1894,6 +2010,7 @@ A806642526905F6C00FFDEBA /* ChannelMatch */, A8BCC4DB26872B6600B72040 /* DebugMode */, A8BCC4D626872A3F00B72040 /* Deeplink */, + F2CD8A7728A240AB00A186B8 /* Exposure */, A8CC22222685E50C00E96A03 /* RemoteConfig */, A82E893C267D918100475757 /* Encrypt */, F277F5B025CF9A43009B5CE6 /* AppPush */, @@ -1904,6 +2021,7 @@ 883BAAAD2669CCE5008105D2 /* Exception */, 881A4224253D7B5E00854F69 /* Location */, CB30C0AB22840F1B0004061D /* Resources */, + F226E66928BC6099000443A7 /* UIRelated */, 4D0571A525A2FC46007F7B72 /* Visualized */, ); path = SensorsAnalyticsSDK; @@ -1952,6 +2070,46 @@ path = Resources; sourceTree = ""; }; + F226E66928BC6099000443A7 /* UIRelated */ = { + isa = PBXGroup; + children = ( + F226E66A28BC6149000443A7 /* SAUIProperties.h */, + F226E66B28BC6149000443A7 /* SAUIProperties.m */, + F226E68A28BC993C000443A7 /* SAUIViewElementProperties.h */, + F26A23CD28BCD18100AB84A6 /* SAUIViewPathProperties.h */, + F200095528BE0363003C5113 /* SAUIInternalProperties.h */, + F200095B28BE0721003C5113 /* UIView+SAInternalProperties.h */, + F200095C28BE0721003C5113 /* UIView+SAInternalProperties.m */, + F20231C828BF43FB0034D8B3 /* UIView+SARNView.h */, + F20231C928BF43FB0034D8B3 /* UIView+SARNView.m */, + F200095F28BE10B5003C5113 /* UIViewController+SAInternalProperties.h */, + F200096028BE10B5003C5113 /* UIViewController+SAInternalProperties.m */, + F200094D28BDADB5003C5113 /* UITableViewCell+SAIndexPath.h */, + F200094E28BDADB5003C5113 /* UITableViewCell+SAIndexPath.m */, + F226E66E28BC62EB000443A7 /* UIView+SAItemPath.h */, + F226E66F28BC62EB000443A7 /* UIView+SAItemPath.m */, + F226E67228BC631B000443A7 /* UIView+SASimilarPath.h */, + F226E67328BC631B000443A7 /* UIView+SASimilarPath.m */, + F200095128BDE7DE003C5113 /* UIAlertController+SASimilarPath.h */, + F200095228BDE7DE003C5113 /* UIAlertController+SASimilarPath.m */, + F226E67628BC63F5000443A7 /* UIView+SAElementType.h */, + F226E67728BC63F5000443A7 /* UIView+SAElementType.m */, + F226E67A28BC6415000443A7 /* UIView+SAElementContent.h */, + F226E67B28BC6415000443A7 /* UIView+SAElementContent.m */, + F226E67E28BC6454000443A7 /* UIView+SAElementPosition.h */, + F226E67F28BC6454000443A7 /* UIView+SAElementPosition.m */, + F226E68228BC646F000443A7 /* UIView+SAElementPath.h */, + F226E68328BC646F000443A7 /* UIView+SAElementPath.m */, + F200095728BE067A003C5113 /* UIView+SAElementID.h */, + F200095828BE067A003C5113 /* UIView+SAElementID.m */, + F26A23C928BCADD800AB84A6 /* UIView+SensorsAnalytics.h */, + F26A23CA28BCADD800AB84A6 /* UIView+SensorsAnalytics.m */, + F20231CE28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.h */, + F20231CF28C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.m */, + ); + path = UIRelated; + sourceTree = ""; + }; F22E1B1826A559DE0033A748 /* AppPageLeave */ = { isa = PBXGroup; children = ( @@ -2038,6 +2196,38 @@ path = AppExtension; sourceTree = ""; }; + F2CD8A7728A240AB00A186B8 /* Exposure */ = { + isa = PBXGroup; + children = ( + F2FBB33328A25835008D10EB /* SAConfigOptions+Exposure.h */, + F2CD8A7828A2410A00A186B8 /* SAExposureConfig.h */, + F2CD8A7928A2410A00A186B8 /* SAExposureConfig.m */, + F2FBB33B28A26B87008D10EB /* SAExposureConfig+Private.h */, + F2FBB33728A2692D008D10EB /* SAExposureData.h */, + F2FBB33828A2692D008D10EB /* SAExposureData.m */, + F2C877B628A65849002BDA2C /* SAExposureData+Private.h */, + F273487728A9EA1500C34E64 /* SAExposureDelegateProxy.h */, + F273487828A9EA1500C34E64 /* SAExposureDelegateProxy.m */, + F286963028A34FFC00276F78 /* SAExposureManager.h */, + F286963128A34FFC00276F78 /* SAExposureManager.m */, + F2FBBBB828A3AAD300F75293 /* SAExposureTimer.h */, + F2FBBBB928A3AAD300F75293 /* SAExposureTimer.m */, + F2FBBBB428A3A77000F75293 /* SAExposureViewObject.h */, + F2FBBBB528A3A77000F75293 /* SAExposureViewObject.m */, + F2FBB33F28A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.h */, + F2FBB34028A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.m */, + F273487328A9E92C00C34E64 /* UIScrollView+ExposureListener.h */, + F273487428A9E92C00C34E64 /* UIScrollView+ExposureListener.m */, + F209BEB528B360A6000CEE49 /* UIView+ExposureIdentifier.h */, + F209BEB628B360A6000CEE49 /* UIView+ExposureIdentifier.m */, + F2FBBBAC28A39D2200F75293 /* UIView+ExposureListener.h */, + F2FBBBAD28A39D2200F75293 /* UIView+ExposureListener.m */, + F2FBBBB028A3A33300F75293 /* UIViewController+ExposureListener.h */, + F2FBBBB128A3A33300F75293 /* UIViewController+ExposureListener.m */, + ); + path = Exposure; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2064,13 +2254,18 @@ 4DAFDCE8282622810074A691 /* SACustomPropertyPlugin.h in Headers */, FCBECDFF27DEDF7F00361D6C /* SADeferredDeepLinkProcessor.h in Headers */, 4D958B222823E1710086A71C /* SASessionPropertyPlugin.h in Headers */, + F226E68C28BC993C000443A7 /* SAUIViewElementProperties.h in Headers */, 4D57261E26206D5300B2AC9F /* SAVisualizedLogger.h in Headers */, 881A420F253D7B5000854F69 /* SAAlertController.h in Headers */, + F226E67028BC62EB000443A7 /* UIView+SAItemPath.h in Headers */, + F2CD8A7A28A2410A00A186B8 /* SAExposureConfig.h in Headers */, F277F5C025CF9A43009B5CE6 /* SAUNUserNotificationCenterDelegateProxy.h in Headers */, + F200095628BE0363003C5113 /* SAUIInternalProperties.h in Headers */, 88F21ADE2806C92B00EDAFF4 /* SAEventResultInterceptor.h in Headers */, A8356DC32656459A00FD64AA /* SAAutoTrackProperty.h in Headers */, FC332DE727672606009122FC /* SADeepLinkProcessor.h in Headers */, 4D2D53C22591EB3A00805141 /* SAReadWriteLock.h in Headers */, + F226E68028BC6454000443A7 /* UIView+SAElementPosition.h in Headers */, F2E4ABA126ECAA8600BA7F01 /* SensorsAnalyticsSDK+DeviceOrientation.h in Headers */, A8356DBD2656459A00FD64AA /* SAAutoTrackUtils.h in Headers */, 881A4201253D7B5000854F69 /* SAAppExtensionDataManager.h in Headers */, @@ -2084,6 +2279,7 @@ A8356DD42656459A00FD64AA /* SAGestureTargetActionModel.h in Headers */, F277F5C725CF9A43009B5CE6 /* SAApplicationDelegateProxy.h in Headers */, 457A7CC427DB2AC900DB0512 /* SANetworkInfoPropertyPlugin.h in Headers */, + F2FBB33D28A26B87008D10EB /* SAExposureConfig+Private.h in Headers */, A8356DC72656459A00FD64AA /* UIScrollView+SAAutoTrack.h in Headers */, 88B58FC82732394E00B83DCC /* SAItemEventObject.h in Headers */, 4DA4BABD28129AA0008B0C5A /* SASuperPropertyPlugin.h in Headers */, @@ -2101,6 +2297,7 @@ A8CC223D2685E50C00E96A03 /* SARemoteConfigCheckOperator.h in Headers */, 881A41F6253D7B5000854F69 /* NSString+SAHashCode.h in Headers */, 4DD1286725F872A4008C0B1E /* NSInvocation+SAHelpers.h in Headers */, + F20231CA28BF43FB0034D8B3 /* UIView+SARNView.h in Headers */, 88EC2DF12757689800EF9778 /* SAStoreManager.h in Headers */, 45A5656A263C174300C9C41B /* SAEventLibObject.h in Headers */, 88EC2DFB275768EE00EF9778 /* SAAESStorePlugin.h in Headers */, @@ -2112,13 +2309,17 @@ 4D4DB2C725B7D19C00938842 /* SAClassHelper.h in Headers */, 4D7B252A2828AF430080BCF0 /* SACarrierNamePropertyPlugin.h in Headers */, 4DA89BC225C2BC1E003ABA43 /* SAReachability.h in Headers */, + F226E67C28BC6415000443A7 /* UIView+SAElementContent.h in Headers */, 886E1E212726AC420084D1B3 /* SADeviceIDPropertyPlugin.h in Headers */, 4DD1285125F872A4008C0B1E /* SAApplicationStateSerializer.h in Headers */, F23CA0082701715E002EEACA /* SensorsAnalyticsSDK+JavaScriptBridge.h in Headers */, FCBECDF327DEDF4200361D6C /* SAQueryDeepLinkProcessor.h in Headers */, A8356DC12656459A00FD64AA /* SAAppEndTracker.h in Headers */, A82E8957267D918100475757 /* SASecretKeyFactory.h in Headers */, + F209BEB728B360A6000CEE49 /* UIView+ExposureIdentifier.h in Headers */, 4DD1284B25F872A4008C0B1E /* SAWebElementView.h in Headers */, + F226E67428BC631B000443A7 /* UIView+SASimilarPath.h in Headers */, + F2FBBBB628A3A77000F75293 /* SAExposureViewObject.h in Headers */, F277F5C925CF9A43009B5CE6 /* SANotificationUtil.h in Headers */, A8CC223E2685E50C00E96A03 /* SARemoteConfigModel.h in Headers */, F2E36483287682E6008D9151 /* SADeviceWhiteList.h in Headers */, @@ -2130,6 +2331,7 @@ F277F5C825CF9A43009B5CE6 /* UNUserNotificationCenter+SAPushClick.h in Headers */, F277F5C825CF9A43009B5CE6 /* UNUserNotificationCenter+SAPushClick.h in Headers */, 88BC3A00281543E000A98EDA /* SAPropertyPlugin.h in Headers */, + F200095328BDE7DE003C5113 /* UIAlertController+SASimilarPath.h in Headers */, 4DD1282125F87225008C0B1E /* SAVisualizedViewPathProperty.h in Headers */, 4D79695A2609D8FE001B0A6C /* SAEventIdentifier.h in Headers */, 45BBC8CF2787DF22004D2D0C /* SAPresetPropertyObject.h in Headers */, @@ -2151,7 +2353,9 @@ A82E8959267D918100475757 /* SAEncryptManager.h in Headers */, 4D1B925A2817ED2D007C31D5 /* SAChannelInfoPropertyPlugin.h in Headers */, 4DD1284E25F872A4008C0B1E /* SATypeDescription.h in Headers */, + F200095D28BE0721003C5113 /* UIView+SAInternalProperties.h in Headers */, A8356DE62656459A00FD64AA /* UIApplication+SAAutoTrack.h in Headers */, + F200094F28BDADB5003C5113 /* UITableViewCell+SAIndexPath.h in Headers */, F23CA00A2701715E002EEACA /* SAJavaScriptBridgeManager.h in Headers */, 4DD1286825F872A4008C0B1E /* SAObjectSerializerContext.h in Headers */, F2E4ABA926ECB19200BA7F01 /* SensorsAnalyticsSDK+DebugMode.h in Headers */, @@ -2176,14 +2380,18 @@ 45A56569263C174300C9C41B /* SAProfileEventObject.h in Headers */, 8809806B27FEE78900EB2B3D /* SAEncryptInterceptor.h in Headers */, 4D14F16C25FC646A00113EA2 /* SAViewNodeTree.h in Headers */, + F226E68428BC646F000443A7 /* UIView+SAElementPath.h in Headers */, 4D01EFB028156D9200A12BCC /* SAEventDurationPropertyPlugin.h in Headers */, A8356DE12656459A00FD64AA /* SAViewElementInfo.h in Headers */, 881A4196253D7B4F00854F69 /* SAConstants.h in Headers */, 4DD1286425F872A4008C0B1E /* SAVisualizedAutoTrackObjectSerializer.h in Headers */, + F200096128BE10B5003C5113 /* UIViewController+SAInternalProperties.h in Headers */, A8BCC4AB2686C42D00B72040 /* SASecretKey.h in Headers */, 88ED6BD027BE2ECB00888FBC /* SAFlowManager.h in Headers */, + F226E66C28BC6149000443A7 /* SAUIProperties.h in Headers */, A8BCC4D926872A3F00B72040 /* SADeepLinkManager.h in Headers */, 880980832803DCBD00EB2B3D /* SAFlushJSONInterceptor.h in Headers */, + F226E67828BC63F5000443A7 /* UIView+SAElementType.h in Headers */, A8BCC4D926872A3F00B72040 /* SADeepLinkManager.h in Headers */, F28997D7273B6D66005E7D5E /* SAGesturePlugin.h in Headers */, 881A419C253D7B4F00854F69 /* SAConstants+Private.h in Headers */, @@ -2197,16 +2405,19 @@ F277F5C325CF9A43009B5CE6 /* UIApplication+SAPushClick.h in Headers */, FCBECDF727DEDF6400361D6C /* SADeepLinkEventProcessor.h in Headers */, F277F5E425CFCE56009B5CE6 /* NSObject+SADelegateProxy.h in Headers */, + F273487928A9EA1500C34E64 /* SAExposureDelegateProxy.h in Headers */, F2E364872876EE94008D9151 /* SASlinkCreator.h in Headers */, F277F5C325CF9A43009B5CE6 /* UIApplication+SAPushClick.h in Headers */, F2B643F62832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.h in Headers */, F277F5E425CFCE56009B5CE6 /* NSObject+SADelegateProxy.h in Headers */, + F2FBB33928A2692D008D10EB /* SAExposureData.h in Headers */, F277F5C325CF9A43009B5CE6 /* UIApplication+SAPushClick.h in Headers */, 4DD128B925F8A003008C0B1E /* SensorsAnalyticsSDK+Visualized.h in Headers */, A8356DCE2656459A00FD64AA /* SAGeneralGestureViewProcessor.h in Headers */, 88EC2DFF2757693600EF9778 /* SAUserDefaultsStorePlugin.h in Headers */, 4D762232284627C1006656DD /* SARepeatFlushInterceptor.h in Headers */, 4DD1286525F872A4008C0B1E /* SAObjectSerializerConfig.h in Headers */, + F26A23CB28BCADD800AB84A6 /* UIView+SensorsAnalytics.h in Headers */, F27EA3CD2739068C00896B3A /* SAEventTrackerPluginManager.h in Headers */, 88F21AE22806CC8400EDAFF4 /* SAPropertyInterceptor.h in Headers */, 4D6D4E1A2833B94A0003433A /* SADeleteRecordInterceptor.h in Headers */, @@ -2216,12 +2427,15 @@ 4D5915CA285B239900A2819C /* SACorrectUserIdInterceptor.h in Headers */, 4D1B925E2817F2F3007C31D5 /* SAReferrerTitlePropertyPlugin.h in Headers */, A8356DBE2656459A00FD64AA /* UIView+SAAutoTrack.h in Headers */, + F2FBBBAE28A39D2200F75293 /* UIView+ExposureListener.h in Headers */, FC0A8C63276334F200109267 /* SADeepLinkConstants.h in Headers */, 4DD1286C25F872A4008C0B1E /* SAClassDescription.h in Headers */, + F20231D028C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.h in Headers */, A8356DC82656459A00FD64AA /* SAScrollViewDelegateProxy.h in Headers */, 4D5F3AC22833B280003A2C0A /* SADatabaseInterceptor.h in Headers */, A8356DBE2656459A00FD64AA /* UIView+SAAutoTrack.h in Headers */, 4D41D9D325FF7E9300D856F4 /* UIViewController+SAElementPath.h in Headers */, + F273487528A9E92C00C34E64 /* UIScrollView+ExposureListener.h in Headers */, A8FEFB2C277C0ADA0011D0BB /* SASessionProperty.h in Headers */, 4DF6FDFB2832660800585FCC /* SAQueryRecordInterceptor.h in Headers */, 4DD1285325F872A4008C0B1E /* SAVisualizedAbstractMessage.h in Headers */, @@ -2230,9 +2444,13 @@ 4D2D53992591EB2100805141 /* SADatabase.h in Headers */, F211742126E9A72C00D65E19 /* SAApplication.h in Headers */, 88F562262817E455000AFBBF /* SAEventObjectFactory.h in Headers */, + F200095928BE067A003C5113 /* UIView+SAElementID.h in Headers */, + F26A23CE28BCD18100AB84A6 /* SAUIViewPathProperties.h in Headers */, 4DD1285825F872A4008C0B1E /* SAPropertyDescription.h in Headers */, 4DD128C825F8A003008C0B1E /* SAVisualPropertiesTracker.h in Headers */, A8356DCB2656459A00FD64AA /* SAGestureTarget.h in Headers */, + F2C877B828A65849002BDA2C /* SAExposureData+Private.h in Headers */, + F2FBB33528A25835008D10EB /* SAConfigOptions+Exposure.h in Headers */, F26FDDD8270312C400E1DF32 /* SAConfigOptions+AppPush.h in Headers */, A82E8951267D918100475757 /* SAEncryptProtocol.h in Headers */, 45A56560263C174300C9C41B /* SAIDFAHelper.h in Headers */, @@ -2244,18 +2462,22 @@ F2E4AB9D26EC86C800BA7F01 /* SensorsAnalyticsSDK+Location.h in Headers */, 45A56563263C174300C9C41B /* SAPropertyValidator.h in Headers */, 4D9B536726382F0100318B1D /* SADeviceOrientationManager.h in Headers */, + F2FBB34128A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.h in Headers */, 8809805727FDA85E00EB2B3D /* SARemoteConfigInterceptor.h in Headers */, 8809808728040CC500EB2B3D /* SAFlushHTTPBodyInterceptor.h in Headers */, + F286963228A34FFC00276F78 /* SAExposureManager.h in Headers */, 8809805427FDA05600EB2B3D /* SAInterceptor.h in Headers */, 4DD1296B25F8E451008C0B1E /* SAViewNodeFactory.h in Headers */, - 4DD1282025F87225008C0B1E /* UIView+SAElementPath.h in Headers */, + 4DD1282025F87225008C0B1E /* UIView+SAVisualizedViewPath.h in Headers */, A82E895A267D918100475757 /* SAAlgorithmProtocol.h in Headers */, 8876235226E739300067F0B4 /* SAPresetPropertyPlugin.h in Headers */, 881A419B253D7B4F00854F69 /* SAConfigOptions.h in Headers */, 4D6AE7EF26086ABE00A9CB08 /* SAVisualizedDebugLogTracker.h in Headers */, 8809805F27FDA9E200EB2B3D /* SAEventValidateInterceptor.h in Headers */, F27EA3C3273900E700896B3A /* SAEventTrackerPlugin.h in Headers */, + F2FBBBB228A3A33300F75293 /* UIViewController+ExposureListener.h in Headers */, F27EA3C9273901E500896B3A /* SAEventTrackerPluginProtocol.h in Headers */, + F2FBBBBA28A3AAD300F75293 /* SAExposureTimer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2367,8 +2589,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F200095E28BE0721003C5113 /* UIView+SAInternalProperties.m in Sources */, 4DD1285225F872A4008C0B1E /* SAApplicationStateSerializer.m in Sources */, 4DA4BABE28129AA0008B0C5A /* SASuperPropertyPlugin.m in Sources */, + F2FBBBBB28A3AAD300F75293 /* SAExposureTimer.m in Sources */, A806642A26905F6C00FFDEBA /* SensorsAnalyticsSDK+SAChannelMatch.m in Sources */, 4DF6FDFC2832660800585FCC /* SAQueryRecordInterceptor.m in Sources */, 4D4DB31B25B7D58C00938842 /* SAEventStore.m in Sources */, @@ -2379,6 +2603,8 @@ 45A5655D263C174300C9C41B /* SAIdentifier.m in Sources */, 4D4DB30F25B7D54D00938842 /* SALog.m in Sources */, 45A5655C263C174300C9C41B /* SAIDFAHelper.m in Sources */, + F226E67528BC631B000443A7 /* UIView+SASimilarPath.m in Sources */, + F226E68128BC6454000443A7 /* UIView+SAElementPosition.m in Sources */, 4D4DB30B25B7D54600938842 /* SAValidator.m in Sources */, 4D4DB30725B7D52700938842 /* SAGzipUtility.m in Sources */, A8356DE32656459A00FD64AA /* SAAppTracker.m in Sources */, @@ -2395,6 +2621,7 @@ 4D4DB2FB25B7D4D800938842 /* SAURLUtils.m in Sources */, 881A4228253D7B5E00854F69 /* SALocationManager.m in Sources */, A8356DDD2656459A00FD64AA /* SAAutoTrackUtils.m in Sources */, + F226E67928BC63F5000443A7 /* UIView+SAElementType.m in Sources */, FCC02F2B26CE4EF700DB8F54 /* SAUserAgent.m in Sources */, F2E4ABAA26ECB19200BA7F01 /* SensorsAnalyticsSDK+DebugMode.m in Sources */, 4D2D53BD2591EB3A00805141 /* SAReadWriteLock.m in Sources */, @@ -2403,6 +2630,7 @@ 45A5656C263C174300C9C41B /* SABaseEventObject.m in Sources */, 4DAFDCE9282622810074A691 /* SACustomPropertyPlugin.m in Sources */, 4D2D53A02591EB2100805141 /* SADatabase.m in Sources */, + F226E67128BC62EB000443A7 /* UIView+SAItemPath.m in Sources */, 4D4DB2CE25B7D19C00938842 /* SAClassHelper.m in Sources */, A8356DC62656459A00FD64AA /* SAScrollViewDelegateProxy.m in Sources */, 4D01ED8D2814FFC600A12BCC /* SADynamicSuperPropertyPlugin.m in Sources */, @@ -2414,6 +2642,7 @@ 4DA89BC125C2BC1E003ABA43 /* SANetwork.m in Sources */, A8356DDC2656459A00FD64AA /* UIView+SAAutoTrack.m in Sources */, 4D6D4E172833B77E0003433A /* SAInsertRecordInterceptor.m in Sources */, + F209BEB828B360A6000CEE49 /* UIView+ExposureIdentifier.m in Sources */, 4DD128C425F8A003008C0B1E /* SAVisualPropertiesConfigSources.m in Sources */, 4D1B92572817DFA3007C31D5 /* SALatestUtmPropertyPlugin.m in Sources */, F277F5E325CFCE56009B5CE6 /* NSObject+SADelegateProxy.m in Sources */, @@ -2427,9 +2656,11 @@ 4DD1286F25F872A4008C0B1E /* SAVisualizedSnapshotMessage.m in Sources */, A8BCC4AC2686C42D00B72040 /* SASecretKey.m in Sources */, A82E895C267D918100475757 /* SAECCEncryptor.m in Sources */, + F20231D128C33ECD0034D8B3 /* UIScrollView+SADelegateHashTable.m in Sources */, 880980842803DCBD00EB2B3D /* SAFlushJSONInterceptor.m in Sources */, 452472C9273E3ACB00865E44 /* SADelegateProxyObject.m in Sources */, A8356DD02656459A00FD64AA /* SAGeneralGestureViewProcessor.m in Sources */, + F2FBBBB328A3A33300F75293 /* UIViewController+ExposureListener.m in Sources */, 4D1B925B2817ED2D007C31D5 /* SAChannelInfoPropertyPlugin.m in Sources */, F22E1B1C26A55C8A0033A748 /* SAAppPageLeaveTracker.m in Sources */, 88F5537227FD6A3100D95E7C /* SAFlowObject.m in Sources */, @@ -2442,6 +2673,7 @@ 8809808828040CC500EB2B3D /* SAFlushHTTPBodyInterceptor.m in Sources */, 8876234D26E61AD80067F0B4 /* SAPropertyPluginManager.m in Sources */, A8CC22352685E50C00E96A03 /* SARemoteConfigCommonOperator.m in Sources */, + F2FBBBB728A3A77000F75293 /* SAExposureViewObject.m in Sources */, 4D9B536826382F0100318B1D /* SADeviceOrientationManager.m in Sources */, 4DD1285425F872A4008C0B1E /* SAEnumDescription.m in Sources */, FCBECDF827DEDF6400361D6C /* SADeepLinkEventProcessor.m in Sources */, @@ -2449,8 +2681,10 @@ F2E4ABA226ECAA8600BA7F01 /* SensorsAnalyticsSDK+DeviceOrientation.m in Sources */, A82E895E267D918100475757 /* SARSAPluginEncryptor.m in Sources */, 4DD1284C25F872A4008C0B1E /* NSInvocation+SAHelpers.m in Sources */, + F20231CB28BF43FB0034D8B3 /* UIView+SARNView.m in Sources */, 886E1E222726AC420084D1B3 /* SADeviceIDPropertyPlugin.m in Sources */, 4D2D537B2591EB0600805141 /* SALogMessage.m in Sources */, + F2FBB33A28A2692D008D10EB /* SAExposureData.m in Sources */, A8356DE22656459A00FD64AA /* SAViewElementInfoFactory.m in Sources */, 881A4181253D7B4F00854F69 /* NSString+SAHashCode.m in Sources */, 4DD1285B25F872A4008C0B1E /* SAValueTransformers.m in Sources */, @@ -2465,10 +2699,12 @@ 88F5538327FD91E400D95E7C /* SASerialQueueInterceptor.m in Sources */, 45A565BD263C17E400C9C41B /* SAReferrerManager.m in Sources */, A8CC22342685E50C00E96A03 /* SARemoteConfigCheckOperator.m in Sources */, + F273487A28A9EA1500C34E64 /* SAExposureDelegateProxy.m in Sources */, 4D79695B2609D8FE001B0A6C /* SAEventIdentifier.m in Sources */, 88EC2DF8275768DE00EF9778 /* SAFileStorePlugin.m in Sources */, A8BCC4DF26872B6600B72040 /* SADebugModeManager.m in Sources */, A8356DBF2656459A00FD64AA /* UIViewController+SAAutoTrack.m in Sources */, + F2FBB34228A2704E008D10EB /* SensorsAnalyticsSDK+Exposure.m in Sources */, 88F5538727FD933600D95E7C /* SADynamicSuperPropertyInterceptor.m in Sources */, A8356DBF2656459A00FD64AA /* UIViewController+SAAutoTrack.m in Sources */, 45A5656B263C174300C9C41B /* SATrackEventObject.m in Sources */, @@ -2488,11 +2724,14 @@ 883ED1A12768AF0900A0706A /* SAAESCrypt.m in Sources */, 45A56565263C174300C9C41B /* SAEventLibObject.m in Sources */, 45A565BE263C17E400C9C41B /* SAAppLifecycle.m in Sources */, + F2CD8A7B28A2410A00A186B8 /* SAExposureConfig.m in Sources */, 4D6AE7EE26086ABE00A9CB08 /* SAVisualizedDebugLogTracker.m in Sources */, 45A56568263C174300C9C41B /* SAPropertyValidator.m in Sources */, A8BCC4A82686C2D800B72040 /* SAConfigOptions+Encrypt.m in Sources */, 881A41FE253D7B5000854F69 /* SAConstants.m in Sources */, F277F5CA25CF9A43009B5CE6 /* UIApplication+SAPushClick.m in Sources */, + F226E68528BC646F000443A7 /* UIView+SAElementPath.m in Sources */, + F200095428BDE7DE003C5113 /* UIAlertController+SASimilarPath.m in Sources */, 4D7B252B2828AF430080BCF0 /* SACarrierNamePropertyPlugin.m in Sources */, 881A4195253D7B4F00854F69 /* SAObject+SAConfigOptions.m in Sources */, 881A41F9253D7B5000854F69 /* SAConfigOptions.m in Sources */, @@ -2500,6 +2739,7 @@ 457A7CC527DB2AC900DB0512 /* SANetworkInfoPropertyPlugin.m in Sources */, F2B643F72832302F00544BD2 /* SensorsAnalyticsSDK+SAAppExtension.m in Sources */, 4DD1286925F872A4008C0B1E /* SATypeDescription.m in Sources */, + F26A23CC28BCADD800AB84A6 /* UIView+SensorsAnalytics.m in Sources */, A8CC22372685E50C00E96A03 /* SARemoteConfigModel.m in Sources */, 4DD128C325F8A003008C0B1E /* SAVisualPropertiesConfig.m in Sources */, A8CC22362685E50C00E96A03 /* SARemoteConfigManager.m in Sources */, @@ -2508,6 +2748,7 @@ 4D4DB2D025B7D19C00938842 /* SADelegateProxy.m in Sources */, 88F5537A27FD6A5400D95E7C /* SANodeObject.m in Sources */, 88F21AE32806CC8400EDAFF4 /* SAPropertyInterceptor.m in Sources */, + F286963328A34FFC00276F78 /* SAExposureManager.m in Sources */, 4DD1284D25F872A4008C0B1E /* SAVisualizedConnection.m in Sources */, 4D6D4E1B2833B94A0003433A /* SADeleteRecordInterceptor.m in Sources */, 8809806427FDAA9C00EB2B3D /* SAIDMappingInterceptor.m in Sources */, @@ -2520,17 +2761,21 @@ 4DA89BC425C2BC1E003ABA43 /* SAReachability.m in Sources */, F277F5CB25CF9A43009B5CE6 /* SAAppPushConstants.m in Sources */, F2E364882876EE94008D9151 /* SASlinkCreator.m in Sources */, + F273487628A9E92C00C34E64 /* UIScrollView+ExposureListener.m in Sources */, + F200095028BDADB5003C5113 /* UITableViewCell+SAIndexPath.m in Sources */, 4DD1286325F872A4008C0B1E /* SAVisualizedAutoTrackObjectSerializer.m in Sources */, 45BD80CF26F0B49700DCC759 /* SAThreadSafeDictionary.m in Sources */, 881A4193253D7B4F00854F69 /* SensorsAnalyticsSDK.m in Sources */, 4D2D53C62591EB3A00805141 /* SACommonUtility.m in Sources */, 4D2D53A22591EB2100805141 /* SAEventRecord.m in Sources */, + F2FBBBAF28A39D2200F75293 /* UIView+ExposureListener.m in Sources */, 4D958B1F2823E0960086A71C /* SAModulePropertyPlugin.m in Sources */, 88098074280037E000EB2B3D /* SAFlushInterceptor.m in Sources */, 4D57261F26206D5300B2AC9F /* SAVisualizedLogger.m in Sources */, 4D5F3ABF2833AF73003A2C0A /* SAUpdateRecordInterceptor.m in Sources */, 4D2D53BB2591EB3A00805141 /* SAWeakPropertyContainer.m in Sources */, 8876235326E739300067F0B4 /* SAPresetPropertyPlugin.m in Sources */, + F200096228BE10B5003C5113 /* UIViewController+SAInternalProperties.m in Sources */, F211742226E9A72C00D65E19 /* SAApplication.m in Sources */, 88ED6BD127BE2ECB00888FBC /* SAFlowManager.m in Sources */, A8356DD72656459A00FD64AA /* SAAppClickTracker.m in Sources */, @@ -2542,6 +2787,7 @@ F28997D8273B6D66005E7D5E /* SAGesturePlugin.m in Sources */, 881A4207253D7B5000854F69 /* SAHTTPSession.m in Sources */, 88B58FC92732394E00B83DCC /* SAItemEventObject.m in Sources */, + F226E66D28BC6149000443A7 /* SAUIProperties.m in Sources */, F277F5C225CF9A43009B5CE6 /* UNUserNotificationCenter+SAPushClick.m in Sources */, A82E8955267D918100475757 /* SAAlgorithmProtocol.m in Sources */, 8809806C27FEE78900EB2B3D /* SAEncryptInterceptor.m in Sources */, @@ -2555,17 +2801,19 @@ 4DD1286D25F872A4008C0B1E /* SAVisualizedManager.m in Sources */, 4DD1286A25F872A4008C0B1E /* SAWebElementView.m in Sources */, F277F5BF25CF9A43009B5CE6 /* SAApplicationDelegateProxy.m in Sources */, + F200095A28BE067A003C5113 /* UIView+SAElementID.m in Sources */, A82E894F267D918100475757 /* SAEncryptManager.m in Sources */, 4D5915CB285B239900A2819C /* SACorrectUserIdInterceptor.m in Sources */, 881A41A1253D7B4F00854F69 /* SAAppExtensionDataManager.m in Sources */, A8356DDA2656459A00FD64AA /* SAAppStartTracker.m in Sources */, 4D4DB2CD25B7D19C00938842 /* SAMethodHelper.m in Sources */, F23CA0092701715E002EEACA /* WKWebView+SABridge.m in Sources */, + F226E67D28BC6415000443A7 /* UIView+SAElementContent.m in Sources */, F2E36484287682E6008D9151 /* SADeviceWhiteList.m in Sources */, 88E6BEDE278ECE5E006B1E4C /* SAAppVersionPropertyPlugin.m in Sources */, 883BAAB12669CD18008105D2 /* SAExceptionManager.m in Sources */, 4DD1286625F872A4008C0B1E /* SAVisualizedAbstractMessage.m in Sources */, - 4DD1282225F87225008C0B1E /* UIView+SAElementPath.m in Sources */, + 4DD1282225F87225008C0B1E /* UIView+SAVisualizedViewPath.m in Sources */, A8356DE02656459A00FD64AA /* SAViewElementInfo.m in Sources */, A8356DD32656459A00FD64AA /* UIGestureRecognizer+SAAutoTrack.m in Sources */, A8356DC92656459A00FD64AA /* UIScrollView+SAAutoTrack.m in Sources */, diff --git a/SensorsAnalyticsSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SensorsAnalyticsSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 0c67376e..f9b0d7c5 100644 --- a/SensorsAnalyticsSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/SensorsAnalyticsSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,5 +1,8 @@ - + + PreviewsEnabled + + diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/SAScrollViewDelegateProxy.m b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/SAScrollViewDelegateProxy.m index aea4718c..056c94e1 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/SAScrollViewDelegateProxy.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/SAScrollViewDelegateProxy.m @@ -29,6 +29,7 @@ #import "UIScrollView+SAAutoTrack.h" #import "SAAutoTrackManager.h" #import +#import "UIScrollView+SADelegateHashTable.h" @implementation SAScrollViewDelegateProxy diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.h b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.h index 6e538de0..4b5c9300 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.h +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.h @@ -24,16 +24,12 @@ NS_ASSUME_NONNULL_BEGIN @interface UITableView (AutoTrack) -@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_delegateHashTable; - - (void)sensorsdata_setDelegate:(id )delegate; @end @interface UICollectionView (AutoTrack) -@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_delegateHashTable; - - (void)sensorsdata_setDelegate:(id )delegate; @end diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.m b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.m index 6ce72fd7..c8bae649 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/Cell/UIScrollView+SAAutoTrack.m @@ -29,24 +29,8 @@ #import "SAConstants+Private.h" #import "SAAutoTrackManager.h" -static const void *kSATableViewDelegateHashTable = &kSATableViewDelegateHashTable; -static const void *kSACollectionViewDelegateHashTable = &kSACollectionViewDelegateHashTable; - @implementation UITableView (AutoTrack) -- (void)setSensorsdata_delegateHashTable:(NSHashTable *)delegateHashTable { - objc_setAssociatedObject(self, kSATableViewDelegateHashTable, delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (NSHashTable *)sensorsdata_delegateHashTable { - NSHashTable *delegateHashTable = objc_getAssociatedObject(self, kSATableViewDelegateHashTable); - if (!delegateHashTable) { - delegateHashTable = [NSHashTable weakObjectsHashTable]; - self.sensorsdata_delegateHashTable = delegateHashTable; - } - return delegateHashTable; -} - - (void)sensorsdata_setDelegate:(id )delegate { //resolve optional selectors [SAScrollViewDelegateProxy resolveOptionalSelectorsForDelegate:delegate]; @@ -71,19 +55,6 @@ - (void)sensorsdata_setDelegate:(id )delegate { @implementation UICollectionView (AutoTrack) -- (void)setSensorsdata_delegateHashTable:(NSHashTable *)delegateHashTable { - objc_setAssociatedObject(self, kSACollectionViewDelegateHashTable, delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (NSHashTable *)sensorsdata_delegateHashTable { - NSHashTable *delegateHashTable = objc_getAssociatedObject(self, kSACollectionViewDelegateHashTable); - if (!delegateHashTable) { - delegateHashTable = [NSHashTable weakObjectsHashTable]; - self.sensorsdata_delegateHashTable = delegateHashTable; - } - return delegateHashTable; -} - - (void)sensorsdata_setDelegate:(id )delegate { //resolve optional selectors [SAScrollViewDelegateProxy resolveOptionalSelectorsForDelegate:delegate]; diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/Gesture/Processor/SAGeneralGestureViewProcessor.m b/SensorsAnalyticsSDK/AutoTrack/AppClick/Gesture/Processor/SAGeneralGestureViewProcessor.m index 11bc3c71..d4e45c96 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/Gesture/Processor/SAGeneralGestureViewProcessor.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/Gesture/Processor/SAGeneralGestureViewProcessor.m @@ -27,6 +27,7 @@ #import "SAAlertController.h" #import "SAAutoTrackUtils.h" #import "SAJSONUtil.h" +#import "SAUIProperties.h" static NSArray * sensorsdata_searchVisualSubView(NSString *type, UIView *view) { NSMutableArray *subViews = [NSMutableArray array]; @@ -116,7 +117,7 @@ - (BOOL)isTrackable { return NO; } // 屏蔽 SAAlertController 的点击事件 - UIViewController *viewController = [SAAutoTrackUtils findNextViewControllerByResponder:self.gesture.view]; + UIViewController *viewController = [SAUIProperties findNextViewControllerByResponder:self.gesture.view]; if ([viewController isKindOfClass:UIAlertController.class] && [viewController.nextResponder isKindOfClass:SAAlertController.class]) { return NO; } @@ -145,7 +146,7 @@ - (BOOL)isTrackable { return NO; } // 屏蔽 SAAlertController 的点击事件 - UIViewController *viewController = [SAAutoTrackUtils findNextViewControllerByResponder:self.gesture.view]; + UIViewController *viewController = [SAUIProperties findNextViewControllerByResponder:self.gesture.view]; if ([viewController isKindOfClass:UIAlertController.class] && [viewController.nextResponder isKindOfClass:SAAlertController.class]) { return NO; } diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/UIApplication+SAAutoTrack.m b/SensorsAnalyticsSDK/AutoTrack/AppClick/UIApplication+SAAutoTrack.m index c228dd1d..e1c656e1 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/UIApplication+SAAutoTrack.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/UIApplication+SAAutoTrack.m @@ -31,6 +31,7 @@ #import "UIViewController+SAAutoTrack.h" #import "SAAutoTrackUtils.h" #import "SAAutoTrackManager.h" +#import "UIView+SensorsAnalytics.h" @implementation UIApplication (AutoTrack) diff --git a/SensorsAnalyticsSDK/AutoTrack/AppClick/UIView+SAAutoTrack.m b/SensorsAnalyticsSDK/AutoTrack/AppClick/UIView+SAAutoTrack.m index 71690fdb..9a2ef0f2 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppClick/UIView+SAAutoTrack.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppClick/UIView+SAAutoTrack.m @@ -28,6 +28,9 @@ #import #import "SAViewElementInfoFactory.h" #import "SAAutoTrackManager.h" +#import "SAUIProperties.h" +#import "UIView+SARNView.h" +#import "UIView+SensorsAnalytics.h" static void *const kSALastAppClickIntervalPropertyName = (void *)&kSALastAppClickIntervalPropertyName; @@ -78,7 +81,7 @@ - (NSString *)sensorsdata_elementContent { return nil; #pragma clang diagnostic pop } - if ([SAAutoTrackUtils isKindOfRNView:self]) { // RN 元素,https://reactnative.dev + if ([self isSensorsdataRNView]) { // RN 元素,https://reactnative.dev NSString *content = [self.accessibilityLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if (content.length > 0) { return content; @@ -122,16 +125,6 @@ - (NSString *)sensorsdata_elementId { return self.sensorsAnalyticsViewID; } -- (UIViewController *)sensorsdata_viewController { - UIViewController *viewController = [SAAutoTrackUtils findNextViewControllerByResponder:self]; - - // 获取当前 controller 作为 screen_name - if (!viewController || [viewController isKindOfClass:UIAlertController.class]) { - viewController = [SAAutoTrackUtils currentViewController]; - } - return viewController; -} - @end @implementation UILabel (AutoTrack) @@ -154,7 +147,7 @@ - (NSString *)sensorsdata_elementContent { - (NSString *)sensorsdata_elementPosition { if ([NSStringFromClass(self.class) isEqualToString:@"UISegment"]) { - NSInteger index = [SAAutoTrackUtils itemIndexForResponder:self]; + NSInteger index = [SAUIProperties indexWithResponder:self]; return index > 0 ? [NSString stringWithFormat:@"%ld", (long)index] : @"0"; } return [super sensorsdata_elementPosition]; @@ -201,7 +194,7 @@ - (NSString *)sensorsdata_elementType { - (NSString *)sensorsdata_elementPosition { // UITabBarItem if ([NSStringFromClass(self.class) isEqualToString:@"UITabBarButton"]) { - NSInteger index = [SAAutoTrackUtils itemIndexForResponder:self]; + NSInteger index = [SAUIProperties indexWithResponder:self]; if (index < 0) { index = 0; } diff --git a/SensorsAnalyticsSDK/AutoTrack/AppPageLeave/SAAppPageLeaveTracker.m b/SensorsAnalyticsSDK/AutoTrack/AppPageLeave/SAAppPageLeaveTracker.m index aa08f2a6..969bb9d4 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppPageLeave/SAAppPageLeaveTracker.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppPageLeave/SAAppPageLeaveTracker.m @@ -30,6 +30,7 @@ #import "SAAppLifecycle.h" #import "SensorsAnalyticsSDK+Private.h" #import "SAAutoTrackManager.h" +#import "SAUIProperties.h" @implementation SAPageLeaveObject @@ -95,7 +96,7 @@ - (void)trackPageEnter:(UIViewController *)viewController { currentURL = [screenAutoTrackerController getScreenUrl]; } currentURL = [currentURL isKindOfClass:NSString.class] ? currentURL : NSStringFromClass(viewController.class); - object.referrerURL = [self referrerURLWithURL:currentURL eventProperties:[SAAutoTrackUtils propertiesWithViewController:(UIViewController *)viewController]]; + object.referrerURL = [self referrerURLWithURL:currentURL eventProperties:[SAUIProperties propertiesWithViewController:(UIViewController *)viewController]]; self.pageLeaveObjects[address] = object; } @@ -148,7 +149,7 @@ - (void)appLifecycleStateWillChange:(NSNotification *)notification { - (NSMutableDictionary *)propertiesWithViewController:(UIViewController *)viewController { NSMutableDictionary *eventProperties = [[NSMutableDictionary alloc] init]; - NSDictionary *autoTrackProperties = [SAAutoTrackUtils propertiesWithViewController:viewController]; + NSDictionary *autoTrackProperties = [SAUIProperties propertiesWithViewController:viewController]; [eventProperties addEntriesFromDictionary:autoTrackProperties]; if (eventProperties[kSAEventPropertyScreenUrl]) { return eventProperties; diff --git a/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/SAAppViewScreenTracker.m b/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/SAAppViewScreenTracker.m index ebb4780f..c0ac48c5 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/SAAppViewScreenTracker.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/SAAppViewScreenTracker.m @@ -32,6 +32,7 @@ #import "SAReferrerManager.h" #import "SAModuleManager.h" #import "SensorsAnalyticsSDK+SAAutoTrack.h" +#import "SAUIProperties.h" @interface SAAppViewScreenTracker () @@ -140,7 +141,7 @@ - (BOOL)isBlackListContainsViewController:(UIViewController *)viewController { - (NSDictionary *)buildWithViewController:(UIViewController *)viewController properties:(NSDictionary *)properties autoTrack:(BOOL)autoTrack { NSMutableDictionary *eventProperties = [[NSMutableDictionary alloc] init]; - NSDictionary *autoTrackProperties = [SAAutoTrackUtils propertiesWithViewController:viewController]; + NSDictionary *autoTrackProperties = [SAUIProperties propertiesWithViewController:viewController]; [eventProperties addEntriesFromDictionary:autoTrackProperties]; if (autoTrack) { diff --git a/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/UIViewController+SAAutoTrack.m b/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/UIViewController+SAAutoTrack.m index 0c46f1c7..207b23ec 100644 --- a/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/UIViewController+SAAutoTrack.m +++ b/SensorsAnalyticsSDK/AutoTrack/AppViewScreen/UIViewController+SAAutoTrack.m @@ -40,27 +40,6 @@ - (BOOL)sensorsdata_isIgnored { return ![[SAAutoTrackManager defaultManager].appClickTracker shouldTrackViewController:self]; } -- (NSString *)sensorsdata_screenName { - return NSStringFromClass([self class]); -} - -- (NSString *)sensorsdata_title { - __block NSString *titleViewContent = nil; - __block NSString *controllerTitle = nil; - [SACommonUtility performBlockOnMainThread:^{ - titleViewContent = self.navigationItem.titleView.sensorsdata_elementContent; - controllerTitle = self.navigationItem.title; - }]; - if (titleViewContent.length > 0) { - return titleViewContent; - } - - if (controllerTitle.length > 0) { - return controllerTitle; - } - return nil; -} - - (void)sa_autotrack_viewDidAppear:(BOOL)animated { // 防止 tabbar 切换,可能漏采 $AppViewScreen 全埋点 if ([self isKindOfClass:UINavigationController.class]) { diff --git a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackProperty.h b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackProperty.h index f4dd7859..f8548967 100644 --- a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackProperty.h +++ b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackProperty.h @@ -22,29 +22,23 @@ #import @protocol SAAutoTrackViewControllerProperty + @property (nonatomic, readonly) BOOL sensorsdata_isIgnored; -@property (nonatomic, copy, readonly) NSString *sensorsdata_screenName; -@property (nonatomic, copy, readonly) NSString *sensorsdata_title; + @end #pragma mark - @protocol SAAutoTrackViewProperty + @property (nonatomic, readonly) BOOL sensorsdata_isIgnored; /// 记录上次触发点击事件的开机时间 @property (nonatomic, assign) NSTimeInterval sensorsdata_timeIntervalForLastAppClick; -@property (nonatomic, copy, readonly) NSString *sensorsdata_elementType; -@property (nonatomic, copy, readonly) NSString *sensorsdata_elementContent; -@property (nonatomic, copy, readonly) NSString *sensorsdata_elementId; - -/// 元素位置,UISegmentedControl 中返回选中的 index, -@property (nonatomic, copy, readonly) NSString *sensorsdata_elementPosition; - -/// 获取 view 所在的 viewController,或者当前的 viewController -@property (nonatomic, readonly) UIViewController *sensorsdata_viewController; @end #pragma mark - @protocol SAAutoTrackCellProperty + - (NSString *)sensorsdata_elementPositionWithIndexPath:(NSIndexPath *)indexPath; + @end diff --git a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.h b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.h index 1f6a3bbd..e85ccde7 100644 --- a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.h +++ b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.h @@ -26,50 +26,14 @@ NS_ASSUME_NONNULL_BEGIN @interface SAAutoTrackUtils : NSObject -#if UIKIT_DEFINE_AS_PROPERTIES -/// 返回当前的 ViewController -@property(class, nonatomic, readonly) UIViewController *currentViewController; -#else -+ (UIViewController *)currentViewController; -#endif - -/** - 获取响应链中的下一个 UIViewController - - @param responder 响应链中的对象 - @return 下一个 ViewController - */ -+ (nullable UIViewController *)findNextViewControllerByResponder:(UIResponder *)responder; - /// 在间隔时间内是否采集 $AppClick 全埋点 + (BOOL)isValidAppClickForObject:(id)object; -/// 判断是否为 RN 元素 -+ (BOOL)isKindOfRNView:(UIView *)view; @end #pragma mark - @interface SAAutoTrackUtils (Property) -/** - 通过响应链找到 对象的序号 - - -1:nextResponder 不是父视图或同类元素,比如 controller.view,涉及路径不带序号 - >=0:元素序号 - - @param responder 响应链中的对象,可以是 UIView 或者 UIViewController - @return 序号 - */ -+ (NSInteger )itemIndexForResponder:(UIResponder *)responder; - -/** - 采集 ViewController 中的事件属性 - - @param viewController 需要采集的 ViewController - @return 事件中与 ViewController 相关的属性字典 - */ -+ (NSMutableDictionary *)propertiesWithViewController:(UIViewController *)viewController; - /** 通过 AutoTrack 控件,获取事件的属性 diff --git a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.m b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.m index c81ca750..c733b13b 100644 --- a/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.m +++ b/SensorsAnalyticsSDK/AutoTrack/SAAutoTrackUtils.m @@ -31,114 +31,15 @@ #import "SAAlertController.h" #import "SAModuleManager.h" #import "SAValidator.h" +#import "UIView+SAInternalProperties.h" +#import "SAUIProperties.h" +#import "UIView+SensorsAnalytics.h" /// 一个元素 $AppClick 全埋点最小时间间隔,100 毫秒 static NSTimeInterval SATrackAppClickMinTimeInterval = 0.1; @implementation SAAutoTrackUtils -+ (UIViewController *)findNextViewControllerByResponder:(UIResponder *)responder { - UIResponder *next = responder; - do { - if (![next isKindOfClass:UIViewController.class]) { - continue; - } - UIViewController *vc = (UIViewController *)next; - if ([vc isKindOfClass:UINavigationController.class]) { - return [self findNextViewControllerByResponder:[(UINavigationController *)vc topViewController]]; - } else if ([vc isKindOfClass:UITabBarController.class]) { - return [self findNextViewControllerByResponder:[(UITabBarController *)vc selectedViewController]]; - } - - UIViewController *parentVC = vc.parentViewController; - if (!parentVC) { - break; - } - if ([parentVC isKindOfClass:UINavigationController.class] || - [parentVC isKindOfClass:UITabBarController.class] || - [parentVC isKindOfClass:UIPageViewController.class] || - [parentVC isKindOfClass:UISplitViewController.class]) { - break; - } - } while ((next = next.nextResponder)); - return [next isKindOfClass:UIViewController.class] ? (UIViewController *)next : nil; -} - -+ (UIViewController *)currentViewController { - __block UIViewController *currentViewController = nil; - void (^ block)(void) = ^{ - UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; - currentViewController = [SAAutoTrackUtils findCurrentViewControllerFromRootViewController:rootViewController isRoot:YES]; - }; - - [SACommonUtility performBlockOnMainThread:block]; - return currentViewController; -} - -+ (BOOL)canFindPresentedViewController:(UIViewController *)viewController { - if (!viewController) { - return NO; - } - if ([viewController isKindOfClass:UIAlertController.class]) { - return NO; - } - if ([@"_UIContextMenuActionsOnlyViewController" isEqualToString:NSStringFromClass(viewController.class)]) { - return NO; - } - return YES; -} - -+ (UIViewController *)findCurrentViewControllerFromRootViewController:(UIViewController *)viewController isRoot:(BOOL)isRoot { - if ([self canFindPresentedViewController:viewController.presentedViewController]) { - return [self findCurrentViewControllerFromRootViewController:viewController.presentedViewController isRoot:NO]; - } - - if ([viewController isKindOfClass:[UITabBarController class]]) { - return [self findCurrentViewControllerFromRootViewController:[(UITabBarController *)viewController selectedViewController] isRoot:NO]; - } - - if ([viewController isKindOfClass:[UINavigationController class]]) { - // 根视图为 UINavigationController - UIViewController *topViewController = [(UINavigationController *)viewController topViewController]; - return [self findCurrentViewControllerFromRootViewController:topViewController isRoot:NO]; - } - - if (viewController.childViewControllers.count > 0) { - if (viewController.childViewControllers.count == 1 && isRoot) { - return [self findCurrentViewControllerFromRootViewController:viewController.childViewControllers.firstObject isRoot:NO]; - } else { - __block UIViewController *currentViewController = viewController; - //从最上层遍历(逆序),查找正在显示的 UITabBarController 或 UINavigationController 类型的 - // 是否包含 UINavigationController 或 UITabBarController 类全屏显示的 controller - [viewController.childViewControllers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIViewController *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - // 判断 obj.view 是否加载,如果尚未加载,调用 obj.view 会触发 viewDidLoad,可能影响客户业务 - if (obj.isViewLoaded) { - CGPoint point = [obj.view convertPoint:CGPointZero toView:nil]; - CGSize windowSize = obj.view.window.bounds.size; - // 正在全屏显示 - BOOL isFullScreenShow = !obj.view.hidden && obj.view.alpha > 0.01 && CGPointEqualToPoint(point, CGPointZero) && CGSizeEqualToSize(obj.view.bounds.size, windowSize); - // 判断类型 - BOOL isStopFindController = [obj isKindOfClass:UINavigationController.class] || [obj isKindOfClass:UITabBarController.class]; - if (isFullScreenShow && isStopFindController) { - currentViewController = [self findCurrentViewControllerFromRootViewController:obj isRoot:NO]; - *stop = YES; - } - } - }]; - return currentViewController; - } - } else if ([viewController respondsToSelector:NSSelectorFromString(@"contentViewController")]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - UIViewController *tempViewController = [viewController performSelector:NSSelectorFromString(@"contentViewController")]; -#pragma clang diagnostic pop - if (tempViewController) { - return [self findCurrentViewControllerFromRootViewController:tempViewController isRoot:NO]; - } - } - return viewController; -} - /// 在间隔时间内是否采集 $AppClick 全埋点 + (BOOL)isValidAppClickForObject:(id)object { if (!object) { @@ -157,139 +58,34 @@ + (BOOL)isValidAppClickForObject:(id)object { return YES; } -// 判断是否为 RN 元素 -+ (BOOL)isKindOfRNView:(UIView *)view { - NSString *className = NSStringFromClass(view.class); - if ([className isEqualToString:@"UISegment"]) { - // 针对 UISegment,可能是 RCTSegmentedControl 或 RNCSegmentedControl 内嵌元素,使用父视图判断是否为 RN 元素 - view = [view superview]; - } - NSArray *classNames = @[@"RCTSurfaceView", @"RCTSurfaceHostingView", @"RCTFPSGraph", @"RCTModalHostView", @"RCTView", @"RCTTextView", @"RCTRootView", @"RCTInputAccessoryView", @"RCTInputAccessoryViewContent", @"RNSScreenContainerView", @"RNSScreen", @"RCTVideo", @"RCTSwitch", @"RCTSlider", @"RCTSegmentedControl", @"RNGestureHandlerButton", @"RNCSlider", @"RNCSegmentedControl"]; - for (NSString *className in classNames) { - Class class = NSClassFromString(className); - if (class && [view isKindOfClass:class]) { - return YES; - } - } - return NO; -} - @end #pragma mark - @implementation SAAutoTrackUtils (Property) -+ (NSInteger)itemIndexForResponder:(UIResponder *)responder { - NSString *classString = NSStringFromClass(responder.class); - - NSInteger index = -1; - NSArray *brothersResponder = [self brothersElementForResponder:responder]; - - for (UIResponder *res in brothersResponder) { - if ([classString isEqualToString:NSStringFromClass(res.class)]) { - index ++; - } - if (res == responder) { - break; - } - } - - /* 序号说明 - -1:nextResponder 不是父视图或同类元素,比如 controller.view,涉及路径不带序号 - >=0:元素序号 - */ - return index; -} - -/// 寻找所有兄弟元素 -+ (NSArray *)brothersElementForResponder:(UIResponder *)responder { - if ([responder isKindOfClass:UIView.class]) { - UIResponder *next = [responder nextResponder]; - if ([next isKindOfClass:UIView.class]) { - NSArray *subViews = [(UIView *)next subviews]; - if ([next isKindOfClass:UISegmentedControl.class]) { - // UISegmentedControl 点击之后,subviews 顺序会变化,需要根据坐标排序才能得到准确序号 - NSArray *brothers = [subViews sortedArrayUsingComparator:^NSComparisonResult (UIView *obj1, UIView *obj2) { - if (obj1.frame.origin.x > obj2.frame.origin.x) { - return NSOrderedDescending; - } else { - return NSOrderedAscending; - } - }]; - return brothers; - } - return subViews; - } - } else if ([responder isKindOfClass:UIViewController.class]) { - return [(UIViewController *)responder parentViewController].childViewControllers; - } - return nil; -} - -+ (NSDictionary *)propertiesWithViewController:(UIViewController *)viewController { - NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; - properties[kSAEventPropertyScreenName] = viewController.sensorsdata_screenName; - properties[kSAEventPropertyTitle] = viewController.sensorsdata_title; - - if ([viewController conformsToProtocol:@protocol(SAAutoTracker)] && - [viewController respondsToSelector:@selector(getTrackProperties)]) { - NSDictionary *trackProperties = [(UIViewController *)viewController getTrackProperties]; - if ([SAValidator isValidDictionary:trackProperties]) { - [properties addEntriesFromDictionary:trackProperties]; - } - } - - return [properties copy]; -} - -+ (NSMutableDictionary *)propertiesWithAutoTrackObject:(id)object { ++ (NSMutableDictionary *)propertiesWithAutoTrackObject:(UIView *)object { return [self propertiesWithAutoTrackObject:object viewController:nil isCodeTrack:NO]; } -+ (NSMutableDictionary *)propertiesWithAutoTrackObject:(id)object isCodeTrack:(BOOL)isCodeTrack { ++ (NSMutableDictionary *)propertiesWithAutoTrackObject:(UIView *)object isCodeTrack:(BOOL)isCodeTrack { return [self propertiesWithAutoTrackObject:object viewController:nil isCodeTrack:isCodeTrack]; } -+ (NSMutableDictionary *)propertiesWithAutoTrackObject:(id)object viewController:(nullable UIViewController *)viewController { ++ (NSMutableDictionary *)propertiesWithAutoTrackObject:(UIView *)object viewController:(nullable UIViewController *)viewController { return [self propertiesWithAutoTrackObject:object viewController:viewController isCodeTrack:NO]; } -+ (NSMutableDictionary *)propertiesWithAutoTrackObject:(id)object viewController:(nullable UIViewController *)viewController isCodeTrack:(BOOL)isCodeTrack { ++ (NSMutableDictionary *)propertiesWithAutoTrackObject:(UIView *)object viewController:(nullable UIViewController *)viewController isCodeTrack:(BOOL)isCodeTrack { if (![object respondsToSelector:@selector(sensorsdata_isIgnored)] || (!isCodeTrack && object.sensorsdata_isIgnored)) { return nil; } - NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; - // ViewID - properties[kSAEventPropertyElementId] = object.sensorsdata_elementId; - viewController = viewController ? : object.sensorsdata_viewController; if (!isCodeTrack && viewController.sensorsdata_isIgnored) { return nil; } - - NSDictionary *dic = [self propertiesWithViewController:viewController]; - [properties addEntriesFromDictionary:dic]; - - properties[kSAEventPropertyElementType] = object.sensorsdata_elementType; - properties[kSAEventPropertyElementContent] = object.sensorsdata_elementContent; - properties[kSAEventPropertyElementPosition] = object.sensorsdata_elementPosition; - - UIView *view = (UIView *)object; - //View Properties - if ([object isKindOfClass:UIView.class]) { - [properties addEntriesFromDictionary:view.sensorsAnalyticsViewProperties]; - } else { - return properties; - } - - // viewPath - NSDictionary *viewPathProperties = [[SAModuleManager sharedInstance] propertiesWithView:view]; - if (viewPathProperties) { - [properties addEntriesFromDictionary:viewPathProperties]; - } - - return properties; + NSDictionary *properties = [SAUIProperties propertiesWithView:object viewController:viewController]; + return [NSMutableDictionary dictionaryWithDictionary:properties]; } @end @@ -301,41 +97,8 @@ @implementation SAAutoTrackUtils (IndexPath) if (![object respondsToSelector:@selector(sensorsdata_isIgnored)] || object.sensorsdata_isIgnored) { return nil; } - NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; - - UIView *cell = (UIView *)[self cellWithScrollView:object selectedAtIndexPath:indexPath]; - if (!cell) { - return nil; - } - - // ViewID - properties[kSAEventPropertyElementId] = object.sensorsdata_elementId; - - UIViewController *viewController = object.sensorsdata_viewController; - if (viewController.sensorsdata_isIgnored) { - return nil; - } - - NSDictionary *dic = [self propertiesWithViewController:viewController]; - [properties addEntriesFromDictionary:dic]; - - properties[kSAEventPropertyElementType] = object.sensorsdata_elementType; - properties[kSAEventPropertyElementContent] = cell.sensorsdata_elementContent; - properties[kSAEventPropertyElementPosition] = [cell sensorsdata_elementPositionWithIndexPath:indexPath]; - - //View Properties - NSDictionary *viewProperties = ((UIView *)object).sensorsAnalyticsViewProperties; - if (viewProperties.count > 0) { - [properties addEntriesFromDictionary:viewProperties]; - } - - // viewPath - NSDictionary *viewPathProperties = [[SAModuleManager sharedInstance] propertiesWithView:(UIView *)cell]; - if (viewPathProperties) { - [properties addEntriesFromDictionary:viewPathProperties]; - } - - return properties; + NSDictionary *properties = [SAUIProperties propertiesWithScrollView:object andIndexPath:indexPath]; + return [NSMutableDictionary dictionaryWithDictionary:properties]; } + (UIView *)cellWithScrollView:(UIScrollView *)scrollView selectedAtIndexPath:(NSIndexPath *)indexPath { diff --git a/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.h b/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.h index f60f1fef..91ea56b2 100644 --- a/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.h +++ b/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.h @@ -23,37 +23,6 @@ NS_ASSUME_NONNULL_BEGIN -@protocol SAUIViewAutoTrackDelegate - -//UITableView -@optional -- (NSDictionary *)sensorsAnalytics_tableView:(UITableView *)tableView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath; - -//UICollectionView -@optional -- (NSDictionary *)sensorsAnalytics_collectionView:(UICollectionView *)collectionView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath; -@end - -@interface UIImage (SensorsAnalytics) -@property (nonatomic, copy) NSString* sensorsAnalyticsImageName; -@end - -@interface UIView (SensorsAnalytics) -/// viewID -@property (nonatomic, copy) NSString* sensorsAnalyticsViewID; - -/// AutoTrack 时,是否忽略该 View -@property (nonatomic, assign) BOOL sensorsAnalyticsIgnoreView; - -/// AutoTrack 发生在 SendAction 之前还是之后,默认是 SendAction 之前 -@property (nonatomic, assign) BOOL sensorsAnalyticsAutoTrackAfterSendAction; - -/// AutoTrack 时,View 的扩展属性 -@property (nonatomic, strong) NSDictionary* sensorsAnalyticsViewProperties; - -@property (nonatomic, weak, nullable) id sensorsAnalyticsDelegate; -@end - /** * @abstract * 自动追踪 (AutoTrack) 中,实现该 Protocal 的 Controller 对象可以通过接口向自动采集的事件中加入属性 diff --git a/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.m b/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.m index c12a25fe..7aee7fbf 100644 --- a/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.m +++ b/SensorsAnalyticsSDK/AutoTrack/SensorsAnalyticsSDK+SAAutoTrack.m @@ -29,6 +29,7 @@ #import "SAModuleManager.h" #import "SAWeakPropertyContainer.h" #include +#import "SAUIProperties.h" @implementation UIImage (SensorsAnalytics) @@ -42,62 +43,12 @@ - (void)setSensorsAnalyticsImageName:(NSString *)sensorsAnalyticsImageName { @end -@implementation UIView (SensorsAnalytics) - -//viewID -- (NSString *)sensorsAnalyticsViewID { - return objc_getAssociatedObject(self, @"sensorsAnalyticsViewID"); -} - -- (void)setSensorsAnalyticsViewID:(NSString *)sensorsAnalyticsViewID { - objc_setAssociatedObject(self, @"sensorsAnalyticsViewID", sensorsAnalyticsViewID, OBJC_ASSOCIATION_COPY_NONATOMIC); -} - -//ignoreView -- (BOOL)sensorsAnalyticsIgnoreView { - return [objc_getAssociatedObject(self, @"sensorsAnalyticsIgnoreView") boolValue]; -} - -- (void)setSensorsAnalyticsIgnoreView:(BOOL)sensorsAnalyticsIgnoreView { - objc_setAssociatedObject(self, @"sensorsAnalyticsIgnoreView", [NSNumber numberWithBool:sensorsAnalyticsIgnoreView], OBJC_ASSOCIATION_ASSIGN); -} - -//afterSendAction -- (BOOL)sensorsAnalyticsAutoTrackAfterSendAction { - return [objc_getAssociatedObject(self, @"sensorsAnalyticsAutoTrackAfterSendAction") boolValue]; -} - -- (void)setSensorsAnalyticsAutoTrackAfterSendAction:(BOOL)sensorsAnalyticsAutoTrackAfterSendAction { - objc_setAssociatedObject(self, @"sensorsAnalyticsAutoTrackAfterSendAction", [NSNumber numberWithBool:sensorsAnalyticsAutoTrackAfterSendAction], OBJC_ASSOCIATION_ASSIGN); -} - -//viewProperty -- (NSDictionary *)sensorsAnalyticsViewProperties { - return objc_getAssociatedObject(self, @"sensorsAnalyticsViewProperties"); -} - -- (void)setSensorsAnalyticsViewProperties:(NSDictionary *)sensorsAnalyticsViewProperties { - objc_setAssociatedObject(self, @"sensorsAnalyticsViewProperties", sensorsAnalyticsViewProperties, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (id)sensorsAnalyticsDelegate { - SAWeakPropertyContainer *container = objc_getAssociatedObject(self, @"sensorsAnalyticsDelegate"); - return container.weakProperty; -} - -- (void)setSensorsAnalyticsDelegate:(id)sensorsAnalyticsDelegate { - SAWeakPropertyContainer *container = [SAWeakPropertyContainer containerWithWeakProperty:sensorsAnalyticsDelegate]; - objc_setAssociatedObject(self, @"sensorsAnalyticsDelegate", container, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -@end - #pragma mark - @implementation SensorsAnalyticsSDK (SAAutoTrack) - (UIViewController *)currentViewController { - return [SAAutoTrackUtils currentViewController]; + return [SAUIProperties currentViewController]; } - (BOOL)isAutoTrackEnabled { diff --git a/SensorsAnalyticsSDK/Core/Interceptor/EventBuild/SAPropertyInterceptor.m b/SensorsAnalyticsSDK/Core/Interceptor/EventBuild/SAPropertyInterceptor.m index abc6d88d..f5f98921 100644 --- a/SensorsAnalyticsSDK/Core/Interceptor/EventBuild/SAPropertyInterceptor.m +++ b/SensorsAnalyticsSDK/Core/Interceptor/EventBuild/SAPropertyInterceptor.m @@ -44,7 +44,9 @@ - (void)processWithInput:(SAFlowData *)input completion:(SAFlowDataCompletion)co SABaseEventObject *object = input.eventObject; // 获取插件采集的所有属性 - NSMutableDictionary *properties = [[SAPropertyPluginManager sharedInstance] propertiesWithFilter:object]; + NSDictionary *pluginProperties = [[SAPropertyPluginManager sharedInstance] propertiesWithFilter:object]; + // 属性合法性校验 + NSMutableDictionary *properties = [SAPropertyValidator validProperties:pluginProperties]; // 事件、公共属性和动态公共属性都需要支持修改 $project, $token, $time object.project = (NSString *)properties[kSAEventCommonOptionalPropertyProject]; diff --git a/SensorsAnalyticsSDK/Core/SAConfigOptions.h b/SensorsAnalyticsSDK/Core/SAConfigOptions.h index 81e4de03..56959bd5 100644 --- a/SensorsAnalyticsSDK/Core/SAConfigOptions.h +++ b/SensorsAnalyticsSDK/Core/SAConfigOptions.h @@ -21,6 +21,7 @@ #import #import "SAStorePlugin.h" #import "SAConstants.h" +#import "SAPropertyPlugin.h" @class SASecretKey; @class SASecurityPolicy; @@ -126,6 +127,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)registerStorePlugin:(id)plugin; +/** + * @abstract + * 注册属性插件 + * + * @param plugin 属性插件对象 + */ +- (void)registerPropertyPlugin:(SAPropertyPlugin *)plugin; + @end NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Core/SAConfigOptions.m b/SensorsAnalyticsSDK/Core/SAConfigOptions.m index c35d0549..036836e4 100644 --- a/SensorsAnalyticsSDK/Core/SAConfigOptions.m +++ b/SensorsAnalyticsSDK/Core/SAConfigOptions.m @@ -25,6 +25,10 @@ #import "SAConfigOptions.h" #import "SensorsAnalyticsSDK+Private.h" +#if __has_include("SAExposureConfig.h") +#import "SAExposureConfig.h" +#endif + /// session 中事件最大间隔 5 分钟(单位为秒) static const NSUInteger kSASessionMaxInterval = 5 * 60; @@ -70,6 +74,10 @@ @interface SAConfigOptions () @property (nonatomic, assign) BOOL enableDeepLink; @property (nonatomic, assign) BOOL enableAutoTrack; +#if __has_include("SAExposureConfig.h") +@property (nonatomic, copy) SAExposureConfig *exposureConfig; +#endif + @end @implementation SAConfigOptions @@ -115,6 +123,10 @@ - (instancetype)initWithServerURL:(NSString *)serverURL launchOptions:(id)launch _storePlugins = [NSMutableArray array]; _ignoredPageLeaveClasses = [NSSet set]; + _propertyPlugins = [NSMutableArray array]; +#if __has_include("SAExposureConfig.h") + _exposureConfig = [[SAExposureConfig alloc] initWithAreaRate:0 stayDuration:0 repeated:YES]; +#endif } return self; } @@ -136,6 +148,7 @@ - (id)copyWithZone:(nullable NSZone *)zone { options.enableSession = self.enableSession; options.eventSessionTimeout = self.eventSessionTimeout; options.disableDeviceId = self.disableDeviceId; + options.propertyPlugins = self.propertyPlugins; #if TARGET_OS_IOS // 支持 https 自签证书 @@ -177,6 +190,9 @@ - (id)copyWithZone:(nullable NSZone *)zone { options.enableDeepLink = self.enableDeepLink; options.enableAutoTrack = self.enableAutoTrack; options.customADChannelURL = self.customADChannelURL; +#if __has_include("SAExposureConfig.h") + options.exposureConfig = self.exposureConfig; +#endif #endif return options; @@ -225,6 +241,13 @@ - (void)ignorePageLeave:(NSArray *)viewControllers { self.ignoredPageLeaveClasses = [NSSet setWithArray:viewControllers]; } +- (void)registerPropertyPlugin:(SAPropertyPlugin *)plugin { + if (![plugin isKindOfClass:SAPropertyPlugin.class]) { + return; + } + [self.propertyPlugins addObject:plugin]; +} + @end diff --git a/SensorsAnalyticsSDK/Core/SAModuleManager.m b/SensorsAnalyticsSDK/Core/SAModuleManager.m index e22e49aa..6353b9fb 100644 --- a/SensorsAnalyticsSDK/Core/SAModuleManager.m +++ b/SensorsAnalyticsSDK/Core/SAModuleManager.m @@ -44,6 +44,7 @@ static NSString * const kSAJavaScriptBridgeModuleName = @"JavaScriptBridge"; static NSString * const kSAExceptionModuleName = @"Exception"; +static NSString * const kSAExposureModuleName = @"Exposure"; @interface SAModuleManager () @@ -126,7 +127,7 @@ - (void)loadModule:(NSString *)moduleName withConfigOptions:(SAConfigOptions *)c return @[kSAJavaScriptBridgeModuleName, kSANotificationModuleName, kSAChannelMatchModuleName, kSADeepLinkModuleName, kSADebugModeModuleName, kSALocationModuleName, kSAAutoTrackModuleName, kSAVisualizedModuleName, kSAEncryptModuleName, - kSADeviceOrientationModuleName, kSAExceptionModuleName, kSARemoteConfigModuleName]; + kSADeviceOrientationModuleName, kSAExceptionModuleName, kSARemoteConfigModuleName, kSAExposureModuleName]; } #pragma mark - Public diff --git a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK+Private.h b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK+Private.h index f3a69666..0eef4427 100644 --- a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK+Private.h +++ b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK+Private.h @@ -83,6 +83,8 @@ //忽略页面浏览时长的页面 @property (nonatomic, copy) NSSet *ignoredPageLeaveClasses; +@property (atomic, strong) NSMutableArray *propertyPlugins; + @end #endif /* SensorsAnalyticsSDK_priv_h */ diff --git a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.h b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.h index d547c130..d07d1df9 100755 --- a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.h +++ b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.h @@ -95,5 +95,13 @@ #import "SensorsAnalyticsSDK+SAAppExtension.h" #endif +#if __has_include("SAConfigOptions+Exposure.h") +#import "SAConfigOptions+Exposure.h" +#endif + +#if __has_include("UIView+SensorsAnalytics.h") +#import "UIView+SensorsAnalytics.h" +#endif + #import "SAAESStorePlugin.h" diff --git a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.m b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.m index 830feac8..4b7da78e 100755 --- a/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.m +++ b/SensorsAnalyticsSDK/Core/SensorsAnalyticsSDK.m @@ -62,7 +62,7 @@ #import "SASessionPropertyPlugin.h" #import "SAEventStore.h" -#define VERSION @"4.4.5" +#define VERSION @"4.4.6" void *SensorsAnalyticsQueueTag = &SensorsAnalyticsQueueTag; @@ -278,6 +278,11 @@ - (void)registerPropertyPlugin { SACarrierNamePropertyPlugin *carrierPlugin = [[SACarrierNamePropertyPlugin alloc] init]; dispatch_async(self.serialQueue, ^{ + // 注册 configOptions 中自定义属性插件 + for (SAPropertyPlugin * plugin in self.configOptions.propertyPlugins) { + [[SAPropertyPluginManager sharedInstance] registerPropertyPlugin:plugin]; + } + // 预置属性 SAPresetPropertyPlugin *presetPlugin = [[SAPresetPropertyPlugin alloc] initWithLibVersion:VERSION]; [[SAPropertyPluginManager sharedInstance] registerPropertyPlugin:presetPlugin]; @@ -294,7 +299,7 @@ - (void)registerPropertyPlugin { // 运营商信息 [[SAPropertyPluginManager sharedInstance] registerPropertyPlugin:carrierPlugin]; - // 静态公共属性 + // 注册静态公共属性插件 SASuperPropertyPlugin *superPropertyPlugin = [[SASuperPropertyPlugin alloc] init]; [[SAPropertyPluginManager sharedInstance] registerPropertyPlugin:superPropertyPlugin]; diff --git a/SensorsAnalyticsSDK/Exposure/SAConfigOptions+Exposure.h b/SensorsAnalyticsSDK/Exposure/SAConfigOptions+Exposure.h new file mode 100644 index 00000000..43766149 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAConfigOptions+Exposure.h @@ -0,0 +1,38 @@ +// +// SAConfigOptions+Exposure.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAConfigOptions.h" +#import "SAExposureConfig.h" +#import "SAExposureData.h" +#import "SensorsAnalyticsSDK+Exposure.h" +#import "UIView+ExposureIdentifier.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAConfigOptions (Exposure) + +/// global exposure config settings, default value with areaRate = 0, stayDuration = 0, repeated = YES +@property (nonatomic, copy) SAExposureConfig *exposureConfig; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureConfig+Private.h b/SensorsAnalyticsSDK/Exposure/SAExposureConfig+Private.h new file mode 100644 index 00000000..f7e24b1d --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureConfig+Private.h @@ -0,0 +1,38 @@ +// +// SAExposureConfig+Private.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SAExposureConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureConfig (Private) + +/// visable area rate, 0 ~ 1, default value is 0 +@property (nonatomic, assign, readonly) CGFloat areaRate; + +/// stay duration, default value is 0, unit is second +@property (nonatomic, assign, readonly) NSTimeInterval stayDuration; + +/// allow repeated exposure or not, default value is YES +@property (nonatomic, assign, readonly) BOOL repeated; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureConfig.h b/SensorsAnalyticsSDK/Exposure/SAExposureConfig.h new file mode 100644 index 00000000..ebcf6b95 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureConfig.h @@ -0,0 +1,37 @@ +// +// SAExposureConfig.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureConfig : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/// init method +/// @param areaRate visable area rate, 0 ~ 1, default value is 0 +/// @param stayDuration stay duration, default value is 0, unit is second +/// @param repeated allow repeated exposure, default value is YES +- (instancetype)initWithAreaRate:(CGFloat)areaRate stayDuration:(NSTimeInterval)stayDuration repeated:(BOOL)repeated; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureConfig.m b/SensorsAnalyticsSDK/Exposure/SAExposureConfig.m new file mode 100644 index 00000000..56f7de3e --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureConfig.m @@ -0,0 +1,55 @@ +// +// SAExposureConfig.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureConfig.h" + +@interface SAExposureConfig () + +@property (nonatomic, assign) CGFloat areaRate; +@property (nonatomic, assign) NSTimeInterval stayDuration; +@property (nonatomic, assign) BOOL repeated; + +@end + +@implementation SAExposureConfig + +- (instancetype)initWithAreaRate:(CGFloat)areaRate stayDuration:(NSTimeInterval)stayDuration repeated:(BOOL)repeated { + self = [super init]; + if (self) { + _areaRate = (areaRate >= 0 && areaRate <= 1 ? areaRate : 0); + _stayDuration = (stayDuration >= 0 ? stayDuration : 0); + _repeated = repeated; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + SAExposureConfig *config = [[[self class] allocWithZone:zone] init]; + config.areaRate = self.areaRate; + config.stayDuration = self.stayDuration; + config.repeated = self.repeated; + return config; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureData+Private.h b/SensorsAnalyticsSDK/Exposure/SAExposureData+Private.h new file mode 100644 index 00000000..69a63449 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureData+Private.h @@ -0,0 +1,34 @@ +// +// SAExposureData+Private.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/12. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SAExposureData.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureData (Private) + +@property (nonatomic, copy) NSString *exposureIdentifier; +@property (nonatomic, copy) NSString *event; +@property (nonatomic, copy) SAExposureConfig *config; +@property (nonatomic, copy) NSDictionary *properties; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureData.h b/SensorsAnalyticsSDK/Exposure/SAExposureData.h new file mode 100644 index 00000000..4d6df83e --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureData.h @@ -0,0 +1,59 @@ +// +// SAExposureData.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SAExposureConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureData : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/// init method +/// @param event event name +- (instancetype)initWithEvent:(NSString *)event; + +/// init method +/// @param event event name +/// @param properties custom event properties, if no, use nil +- (instancetype)initWithEvent:(NSString *)event properties:(nullable NSDictionary *)properties; + +/// init method +/// @param event event name +/// @param properties custom event properties, if no, use nil +/// @param exposureIdentifier identifier for view +- (instancetype)initWithEvent:(NSString *)event properties:(nullable NSDictionary *)properties exposureIdentifier:(nullable NSString *)exposureIdentifier; + +/// init method +/// @param event event name +/// @param properties custom event properties, if no, use nil +/// @param config exposure config, if nil, use global config in SAConfigOptions +- (instancetype)initWithEvent:(NSString *)event properties:(nullable NSDictionary *)properties config:(nullable SAExposureConfig *)config; + +/// init method +/// @param event event name +/// @param properties custom event properties, if no, use nil +/// @param exposureIdentifier identifier for view +/// @param config exposure config, if nil, use global config in SAConfigOptions +- (instancetype)initWithEvent:(NSString *)event properties:(nullable NSDictionary *)properties exposureIdentifier:(nullable NSString *)exposureIdentifier config:(nullable SAExposureConfig *)config; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureData.m b/SensorsAnalyticsSDK/Exposure/SAExposureData.m new file mode 100644 index 00000000..4f589336 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureData.m @@ -0,0 +1,64 @@ +// +// SAExposureData.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureData.h" + +@interface SAExposureData () + +@property (nonatomic, copy) NSString *event; +@property (nonatomic, copy) NSDictionary *properties; +@property (nonatomic, copy) NSString *exposureIdentifier; +@property (nonatomic, copy) SAExposureConfig *config; + +@end + +@implementation SAExposureData + +- (instancetype)initWithEvent:(NSString *)event { + return [self initWithEvent:event properties:nil exposureIdentifier:nil config:nil]; +} + +- (instancetype)initWithEvent:(NSString *)event properties:(NSDictionary *)properties { + return [self initWithEvent:event properties:properties exposureIdentifier:nil config:nil]; +} + +- (instancetype)initWithEvent:(NSString *)event properties:(NSDictionary *)properties exposureIdentifier:(NSString *)exposureIdentifier { + return [self initWithEvent:event properties:properties exposureIdentifier:exposureIdentifier config:nil]; +} + +- (instancetype)initWithEvent:(NSString *)event properties:(NSDictionary *)properties config:(SAExposureConfig *)config { + return [self initWithEvent:event properties:properties exposureIdentifier:nil config:config]; +} + +- (instancetype)initWithEvent:(NSString *)event properties:(NSDictionary *)properties exposureIdentifier:(NSString *)exposureIdentifier config:(SAExposureConfig *)config { + self = [super init]; + if (self) { + _event = event; + _properties = properties; + _exposureIdentifier = exposureIdentifier; + _config = config; + } + return self; +} +@end diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.h b/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.h new file mode 100644 index 00000000..418b6042 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.h @@ -0,0 +1,29 @@ +// +// SAExposureDelegateProxy.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/15. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SADelegateProxy.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureDelegateProxy : SADelegateProxy + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.m b/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.m new file mode 100644 index 00000000..ee6715fc --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureDelegateProxy.m @@ -0,0 +1,123 @@ +// +// SAExposureDelegateProxy.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/15. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureDelegateProxy.h" +#import +#import "SAExposureViewObject.h" +#import "SAExposureManager.h" +#import "UIScrollView+SADelegateHashTable.h" +#import +#import "SALog.h" + +@implementation SAExposureDelegateProxy + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { + // 防止某些场景下循环调用 + if ([tableView.sensorsdata_exposure_delegateHashTable containsObject:self]) { + return; + } + [tableView.sensorsdata_exposure_delegateHashTable addObject:self]; + + SAExposureViewObject *exposureViewObject = [[SAExposureManager defaultManager] exposureViewWithView:cell]; + exposureViewObject.state = (exposureViewObject.state == SAExposureViewStateExposing ? SAExposureViewStateExposing : SAExposureViewStateVisible); + exposureViewObject.scrollView = tableView; + exposureViewObject.indexPath = indexPath; + + //invoke original + SEL methodSelector = @selector(tableView:willDisplayCell:forRowAtIndexPath:); + if (class_getInstanceMethod(tableView.delegate.class, methodSelector)) { + [SAExposureDelegateProxy invokeWithTarget:self selector:methodSelector, tableView, cell, indexPath]; + } + + [tableView.sensorsdata_exposure_delegateHashTable removeAllObjects]; +} + +- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { + // 防止某些场景下循环调用 + if ([tableView.sensorsdata_exposure_delegateHashTable containsObject:self]) { + return; + } + [tableView.sensorsdata_exposure_delegateHashTable addObject:self]; + + SAExposureViewObject *exposureViewObject = [[SAExposureManager defaultManager] exposureViewWithView:cell]; + if (![tableView.indexPathsForVisibleRows containsObject:indexPath]) { + exposureViewObject.state = SAExposureViewStateInvisible; + } + + //invoke original + SEL methodSelector = @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:); + if (class_getInstanceMethod(tableView.delegate.class, methodSelector)) { + [SAExposureDelegateProxy invokeWithTarget:self selector:methodSelector, tableView, cell, indexPath]; + } + + [tableView.sensorsdata_exposure_delegateHashTable removeAllObjects]; +} + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // 防止某些场景下循环调用 + if ([collectionView.sensorsdata_exposure_delegateHashTable containsObject:self]) { + return; + } + [collectionView.sensorsdata_exposure_delegateHashTable addObject:self]; + + SAExposureViewObject *exposureViewObject = [[SAExposureManager defaultManager] exposureViewWithView:cell]; + exposureViewObject.state = (exposureViewObject.state == SAExposureViewStateExposing ? SAExposureViewStateExposing : SAExposureViewStateVisible); + exposureViewObject.scrollView = collectionView; + exposureViewObject.indexPath = indexPath; + + //invoke original + SEL methodSelector = @selector(collectionView:willDisplayCell:forItemAtIndexPath:); + if (class_getInstanceMethod(collectionView.delegate.class, methodSelector)) { + [SAExposureDelegateProxy invokeWithTarget:self selector:methodSelector, collectionView, cell, indexPath]; + } + + [collectionView.sensorsdata_exposure_delegateHashTable removeAllObjects]; +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // 防止某些场景下循环调用 + if ([collectionView.sensorsdata_exposure_delegateHashTable containsObject:self]) { + return; + } + [collectionView.sensorsdata_exposure_delegateHashTable addObject:self]; + + SAExposureViewObject *exposureViewObject = [[SAExposureManager defaultManager] exposureViewWithView:cell]; + if (![collectionView.indexPathsForVisibleItems containsObject:indexPath]) { + exposureViewObject.state = SAExposureViewStateInvisible; + } + + //invoke original + SEL methodSelector = @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:); + if (class_getInstanceMethod(collectionView.delegate.class, methodSelector)) { + [SAExposureDelegateProxy invokeWithTarget:self selector:methodSelector, collectionView, cell, indexPath]; + } + + [collectionView.sensorsdata_exposure_delegateHashTable removeAllObjects]; +} + ++ (NSSet *)optionalSelectors { + return [NSSet setWithArray:@[@"tableView:willDisplayCell:forRowAtIndexPath:", @"tableView:didEndDisplayingCell:forRowAtIndexPath:", @"collectionView:willDisplayCell:forItemAtIndexPath:", @"collectionView:didEndDisplayingCell:forItemAtIndexPath:"]]; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureManager.h b/SensorsAnalyticsSDK/Exposure/SAExposureManager.h new file mode 100644 index 00000000..debbf28c --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureManager.h @@ -0,0 +1,46 @@ +// +// SAExposureManager.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "SAConfigOptions.h" +#import "SAModuleProtocol.h" +#import "SAExposureViewObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureManager : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/// singleton instance ++ (instancetype)defaultManager; + +@property (nonatomic, strong) SAConfigOptions *configOptions; +@property (nonatomic, assign, getter=isEnable) BOOL enable; +@property (nonatomic, strong) NSMutableArray *exposureViewObjects; + +- (void)addExposureView:(UIView *)view withData:(SAExposureData *)data; +- (void)removeExposureView:(UIView *)view withExposureIdentifier:(nullable NSString *)identifier; + +- (SAExposureViewObject *)exposureViewWithView:(UIView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureManager.m b/SensorsAnalyticsSDK/Exposure/SAExposureManager.m new file mode 100644 index 00000000..07c89604 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureManager.m @@ -0,0 +1,200 @@ +// +// SAExposureManager.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureManager.h" +#import "SAConfigOptions+Exposure.h" +#import "SAExposureData+Private.h" +#import "UIView+ExposureIdentifier.h" +#import "SAExposureConfig+Private.h" +#import "SASwizzle.h" +#import "UIView+ExposureListener.h" +#import "UIScrollView+ExposureListener.h" +#import "UIViewController+ExposureListener.h" +#import "SAMethodHelper.h" +#import "SALog.h" + + +static NSString *const kSAExposureViewMark = @"sensorsdata_exposure_mark"; + +@implementation SAExposureManager + ++ (instancetype)defaultManager { + static dispatch_once_t onceToken; + static SAExposureManager *manager = nil; + dispatch_once(&onceToken, ^{ + manager = [[SAExposureManager alloc] init]; + [manager addListener]; + [manager swizzleMethods]; + }); + return manager; +} + +- (void)setConfigOptions:(SAConfigOptions *)configOptions { + _configOptions = configOptions; + self.enable = YES; +} + +- (void)addExposureView:(UIView *)view withData:(SAExposureData *)data { + if (!view) { + SALogError(@"View to expose should not be nil"); + return; + } + if (!data.event || ([data.event isKindOfClass:[NSString class]] && data.event.length == 0)) { + SALogError(@"Event name should not be empty or nil"); + return; + } + if (!data.config) { + data.config = self.configOptions.exposureConfig; + } + __block BOOL exist = NO; + [self.exposureViewObjects enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(SAExposureViewObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (!obj.view) { + [obj clear]; + [self.exposureViewObjects removeObject:obj]; + return; + } + if ((!data.exposureIdentifier && obj.view == view) || (data.exposureIdentifier && [obj.exposureData.exposureIdentifier isEqualToString:data.exposureIdentifier])) { + obj.exposureData = data; + obj.view = view; + exist = YES; + *stop = YES; + } + }]; + if (exist) { + return; + } + SAExposureViewObject *exposureViewObject = [[SAExposureViewObject alloc] initWithView:view exposureData:data]; + exposureViewObject.view.sensorsdata_exposureMark = kSAExposureViewMark; + //get view related items, such as viewController, scrollView, state + if (![view isKindOfClass:[UITableViewCell class]] && ![view isKindOfClass:[UICollectionViewCell class]]) { + exposureViewObject.scrollView = (UIScrollView *)[self nearbyScrollViewByView:view]; + } + [exposureViewObject addExposureViewObserver]; + [self.exposureViewObjects addObject:exposureViewObject]; + [exposureViewObject exposureConditionCheck]; +} + +- (void)removeExposureView:(UIView *)view withExposureIdentifier:(NSString *)identifier { + if (!view) { + return; + } + [self.exposureViewObjects enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(SAExposureViewObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (obj.view == view) { + if (!identifier || [obj.exposureData.exposureIdentifier isEqualToString:identifier]) { + [obj clear]; + [self.exposureViewObjects removeObject:obj]; + } + *stop = YES; + } + }]; +} + +- (SAExposureViewObject *)exposureViewWithView:(UIView *)view { + if (!view) { + return nil; + } + for (SAExposureViewObject *exposureViewObject in self.exposureViewObjects) { + if (exposureViewObject.view != view) { + continue; + } + if (!exposureViewObject.exposureData.exposureIdentifier) { + return exposureViewObject; + } + if (exposureViewObject.exposureData.exposureIdentifier && view.exposureIdentifier && [exposureViewObject.exposureData.exposureIdentifier isEqualToString:view.exposureIdentifier]) { + return exposureViewObject; + } + return nil; + } + return nil; +} + +- (UIView *)nearbyScrollViewByView:(UIView *)view { + UIView *superView = view.superview; + if ([superView isKindOfClass:[UIScrollView class]] || !superView) { + return superView; + } + return [self nearbyScrollViewByView:superView]; +} + +- (void)addListener { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeVisible:) name:UIWindowDidBecomeVisibleNotification object:nil]; +} + +- (void)swizzleMethods { + [SAMethodHelper swizzleRespondsToSelector]; + [UIView sa_swizzleMethod:@selector(didMoveToSuperview) withMethod:@selector(sensorsdata_didMoveToSuperview) error:NULL]; + BOOL isSuccess = [UITableView sa_swizzleMethod:@selector(setDelegate:) withMethod:@selector(sensorsdata_exposure_setDelegate:) error:NULL]; + [UICollectionView sa_swizzleMethod:@selector(setDelegate:) withMethod:@selector(sensorsdata_exposure_setDelegate:) error:NULL]; + [UIViewController sa_swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(sensorsdata_exposure_viewDidAppear:) error:NULL]; + [UIViewController sa_swizzleMethod:@selector(viewDidDisappear:) withMethod:@selector(sensorsdata_exposure_viewDidDisappear:) error:NULL]; +} + +- (void)applicationDidEnterBackground { + for (SAExposureViewObject *exposureViewObject in self.exposureViewObjects) { + if (exposureViewObject.state == SAExposureViewStateExposing || exposureViewObject.state == SAExposureViewStateVisible) { + exposureViewObject.state = SAExposureViewStateBackgroundInvisible; + [exposureViewObject.timer stop]; + } + } +} + +- (void)applicationDidBecomeActive { + for (SAExposureViewObject *exposureViewObject in self.exposureViewObjects) { + if (exposureViewObject.state == SAExposureViewStateBackgroundInvisible) { + exposureViewObject.state = SAExposureViewStateVisible; + if (!exposureViewObject.exposureData.config.repeated && exposureViewObject.lastExposure > 0) { + continue; + } + // convert to string to compare float number + NSComparisonResult result = [[NSString stringWithFormat:@"%.2f",exposureViewObject.lastAreaRate] compare:[NSString stringWithFormat:@"%.2f",exposureViewObject.exposureData.config.areaRate]]; + if (result != NSOrderedAscending) { + [exposureViewObject.timer start]; + } + } + } +} + +- (void)windowDidBecomeVisible:(NSNotification *)notification { + UIWindow *visibleWindow = notification.object; + if (!visibleWindow) { + return; + } + + SAExposureViewObject *exposureViewObject = [self exposureViewWithView:visibleWindow]; + if (!exposureViewObject) { + return; + } + [exposureViewObject exposureConditionCheck]; +} + +-(NSMutableArray *)exposureViewObjects { + if (!_exposureViewObjects) { + _exposureViewObjects = [NSMutableArray array]; + } + return _exposureViewObjects; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureTimer.h b/SensorsAnalyticsSDK/Exposure/SAExposureTimer.h new file mode 100644 index 00000000..4b701af8 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureTimer.h @@ -0,0 +1,39 @@ +// +// SAExposureTimer.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SAExposureTimer : NSObject + +@property (nonatomic, copy, nullable) void (^completeBlock)(void); + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDuration:(NSTimeInterval)duration completeBlock:(nullable void (^)(void))completeBlock; + +- (void)start; +- (void)stop; + +- (void)invalidate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureTimer.m b/SensorsAnalyticsSDK/Exposure/SAExposureTimer.m new file mode 100644 index 00000000..18ce3f0e --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureTimer.m @@ -0,0 +1,99 @@ +// +// SAExposureTimer.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureTimer.h" + +@interface SAExposureTimer () + +@property (nonatomic, assign) NSTimeInterval duration; +@property (nonatomic, assign) BOOL isCountingdown; +@property (nonatomic, strong) dispatch_queue_t queue; +@property (nonatomic, strong) dispatch_source_t source; + +@end + +@implementation SAExposureTimer + +- (instancetype)initWithDuration:(NSTimeInterval)duration completeBlock:(nullable void (^)(void))completeBlock { + self = [super init]; + if (self) { + _duration = duration; + _completeBlock = completeBlock; + _isCountingdown = NO; + NSString *queueLabel = [NSString stringWithFormat:@"cn.SensorsFocus.TimerQueue.%p", self]; + _queue = dispatch_queue_create([queueLabel UTF8String], DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)start { + if (self.isCountingdown) { + return; + } + if (!self.source) { + [self createTimer]; + } else { + [self releaseTimer]; + } + + dispatch_resume(self.source); + self.isCountingdown = YES; +} + +- (void)stop { + self.isCountingdown = NO; + [self releaseTimer]; +} + +- (void)fire { + if (self.completeBlock) { + self.completeBlock(); + } +} + +- (void)invalidate { + [self stop]; +} + +- (void)releaseTimer { + if (self.source) { + dispatch_source_cancel(self.source); + self.source = nil; + } +} + +- (void)createTimer { + __weak typeof(self) weakSelf = self; + self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); + dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, self.duration * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0); + dispatch_source_set_event_handler(_source, ^{ + [weakSelf fire]; + }); +} + +- (void)dealloc { + [self invalidate]; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.h b/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.h new file mode 100644 index 00000000..3a91393b --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.h @@ -0,0 +1,61 @@ +// +// SAExposureView.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "SAExposureData.h" +#import "SAExposureTimer.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, SAExposureViewState) { + SAExposureViewStateVisible, + SAExposureViewStateInvisible, + SAExposureViewStateBackgroundInvisible, + SAExposureViewStateExposing, +}; + +typedef NS_ENUM(NSUInteger, SAExposureViewType) { + SAExposureViewTypeNormal, + SAExposureViewTypeCell, +}; + +@interface SAExposureViewObject : NSObject + +@property (nonatomic, weak) UIView *view; +@property (nonatomic, assign) SAExposureViewState state; +@property (nonatomic, assign) SAExposureViewType type; +@property (nonatomic, strong) SAExposureData *exposureData; +@property (nonatomic, weak, readonly) UIViewController *viewController; +@property (nonatomic, weak) UIScrollView *scrollView; +@property (nonatomic, strong) NSIndexPath *indexPath; +@property (nonatomic, assign) NSTimeInterval lastExposure; +@property (nonatomic, assign) CGFloat lastAreaRate; +@property (nonatomic, strong) SAExposureTimer *timer; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithView:(UIView *)view exposureData:(SAExposureData *)exposureData; + +- (void)addExposureViewObserver; +- (void)clear; +- (void)exposureConditionCheck; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.m b/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.m new file mode 100644 index 00000000..9c7f1e0c --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SAExposureViewObject.m @@ -0,0 +1,396 @@ +// +// SAExposureView.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAExposureViewObject.h" +#import "SensorsAnalyticsSDK.h" +#import "SAModuleManager.h" +#import "SAExposureData+Private.h" +#import "SAExposureConfig+Private.h" +#import "SAValidator.h" +#import "SAUIProperties.h" +#import "SAConstants+Private.h" +#import "UIView+ExposureListener.h" +#import "SALog.h" +#import "UIView+SAInternalProperties.h" + +static void * const kSAExposureViewFrameContext = (void*)&kSAExposureViewFrameContext; +static void * const kSAExposureViewAlphaContext = (void*)&kSAExposureViewAlphaContext; +static void * const kSAExposureViewHiddenContext = (void*)&kSAExposureViewHiddenContext; +static void * const kSAExposureViewContentOffsetContext = (void*)&kSAExposureViewContentOffsetContext; + +@implementation SAExposureViewObject + +- (instancetype)initWithView:(UIView *)view exposureData:(SAExposureData *)exposureData { + self = [super init]; + if (self) { + _view = view; + _exposureData = exposureData; + _state = SAExposureViewStateInvisible; + _type = SAExposureViewTypeNormal; + _lastExposure = 0; + __weak typeof(self) weakSelf = self; + _timer = [[SAExposureTimer alloc] initWithDuration:exposureData.config.stayDuration completeBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf triggerExposure]; + }); + }]; + } + return self; +} + +- (void)addExposureViewObserver { + [self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kSAExposureViewFrameContext]; + [self.view addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kSAExposureViewAlphaContext]; + [self.view addObserver:self forKeyPath:@"hidden" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kSAExposureViewHiddenContext]; + self.view.sensorsdata_exposure_observer = self; +} + +- (void)removeExposureObserver { + [self removeExposureViewObserver]; + [self removeExposureScrollViewObserver]; +} + +- (void)removeExposureViewObserver { + if (!self.view.observationInfo || self.view.sensorsdata_exposure_observer != self) { + return; + } + @try { + [self.view removeObserver:self forKeyPath:@"frame" context:kSAExposureViewFrameContext]; + [self.view removeObserver:self forKeyPath:@"alpha" context:kSAExposureViewAlphaContext]; + [self.view removeObserver:self forKeyPath:@"hidden" context:kSAExposureViewHiddenContext]; + } @catch (NSException *exception) { + SALogError(@"%@", exception); + } @finally { + self.view.sensorsdata_exposure_observer = nil; + } +} + +- (void)removeExposureScrollViewObserver { + if (!self.scrollView.observationInfo || self.scrollView.sensorsdata_exposure_observer != self) { + return; + } + @try { + [self.scrollView removeObserver:self forKeyPath:@"contentOffset" context:kSAExposureViewContentOffsetContext]; + } @catch (NSException *exception) { + SALogError(@"%@", exception); + } @finally { + self.scrollView.sensorsdata_exposure_observer = nil; + } +} + +- (void)clear { + [self.timer invalidate]; + [self removeExposureObserver]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context == kSAExposureViewFrameContext) { + [self observeFrameChange:change]; + } else if (context == kSAExposureViewAlphaContext) { + [self observeAlphaChange:change]; + } else if (context == kSAExposureViewHiddenContext) { + [self observeHiddenChange:change]; + } else if (context == kSAExposureViewContentOffsetContext) { + [self observeContentOffsetChange:change]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)observeFrameChange:(NSDictionary *)change { + NSValue *newValue = change[NSKeyValueChangeNewKey]; + NSValue *oldValue = change[NSKeyValueChangeOldKey]; + if (![newValue isKindOfClass:[NSValue class]] || ![oldValue isKindOfClass:[NSValue class]]) { + return; + } + if ([newValue isEqualToValue:oldValue]) { + return; + } + if ([self.view isKindOfClass:[UITableViewCell class]] || [self.view isKindOfClass:[UICollectionViewCell class]]) { + if (self.state == SAExposureViewStateInvisible || self.state == SAExposureViewStateExposing) { + return; + } + } + [self exposureConditionCheck]; +} + +- (void)observeAlphaChange:(NSDictionary *)change { + NSNumber *newValue = change[NSKeyValueChangeNewKey]; + NSNumber *oldValue = change[NSKeyValueChangeOldKey]; + if (![newValue isKindOfClass:[NSNumber class]] || ![oldValue isKindOfClass:[NSNumber class]]) { + return; + } + if ([newValue isEqualToNumber:oldValue]) { + return; + } + float oldAlphaValue = oldValue.floatValue; + float newAlphaValue = newValue.floatValue; + if (oldAlphaValue > 0.01 && newAlphaValue <= 0.01) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (oldAlphaValue <= 0.01 && newAlphaValue > 0.01) { + if (self.lastAreaRate >= self.exposureData.config.areaRate) { + [self.timer start]; + self.state = SAExposureViewStateVisible; + return; + } + [self exposureConditionCheck]; + return; + } +} + +- (void)observeHiddenChange:(NSDictionary *)change { + NSNumber *newValue = change[NSKeyValueChangeNewKey]; + NSNumber *oldValue = change[NSKeyValueChangeOldKey]; + if (![newValue isKindOfClass:[NSNumber class]] || ![oldValue isKindOfClass:[NSNumber class]]) { + return; + } + if ([newValue isEqualToNumber:oldValue]) { + return; + } + BOOL newHiddenValue = [newValue boolValue]; + BOOL oldHiddenValue = [oldValue boolValue]; + if (oldHiddenValue && !newHiddenValue) { + if (self.lastAreaRate >= self.exposureData.config.areaRate) { + [self.timer start]; + self.state = SAExposureViewStateVisible; + return; + } + [self exposureConditionCheck]; + return; + } + if (!oldHiddenValue && newHiddenValue) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + } +} + +- (void)observeContentOffsetChange:(NSDictionary *)change { + NSValue *newValue = change[NSKeyValueChangeNewKey]; + NSValue *oldValue = change[NSKeyValueChangeOldKey]; + if (![newValue isKindOfClass:[NSValue class]] || ![oldValue isKindOfClass:[NSValue class]]) { + return; + } + if ([newValue isEqualToValue:oldValue]) { + return; + } + if ([self.view isKindOfClass:[UITableViewCell class]] || [self.view isKindOfClass:[UICollectionViewCell class]]) { + if (self.state == SAExposureViewStateInvisible || self.state == SAExposureViewStateExposing) { + return; + } + } + [self exposureConditionCheck]; +} + +- (void)exposureConditionCheck { + if (!self.view) { + return; + } + + if (!self.exposureData.config.repeated && self.lastExposure > 0) { + return; + } + + if ([self.view isKindOfClass:[UIWindow class]] && self.view != [self topWindow]) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (!self.view.window) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (self.view.isHidden) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (self.view.alpha <= 0.01) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (CGRectEqualToRect(self.view.frame, CGRectZero)) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + + CGRect visibleRect = CGRectZero; + if ([self.view isKindOfClass:[UIWindow class]]) { + visibleRect = CGRectIntersection(self.view.frame, [UIScreen mainScreen].bounds); + } else { + CGRect viewToWindowRect = [self.view convertRect:self.view.bounds toView:self.view.window]; + CGRect windowRect = self.view.window.bounds; + CGRect viewVisableRect = CGRectIntersection(viewToWindowRect, windowRect); + visibleRect = viewVisableRect; + if (self.scrollView) { + CGRect scrollViewToWindowRect = [self.scrollView convertRect:self.scrollView.bounds toView:self.scrollView.window]; + CGRect scrollViewVisableRect = CGRectIntersection(scrollViewToWindowRect, windowRect); + visibleRect = CGRectIntersection(viewVisableRect, scrollViewVisableRect); + } + } + + if (CGRectIsNull(visibleRect)) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + self.lastAreaRate = 0; + return; + } + CGFloat visableRate = (visibleRect.size.width * visibleRect.size.height) / (self.view.bounds.size.width * self.view.bounds.size.height); + self.lastAreaRate = visableRate; + if (visableRate <= 0) { + self.state = SAExposureViewStateInvisible; + [self.timer stop]; + return; + } + if (self.state == SAExposureViewStateExposing) { + return; + } + // convert to string to compare float number + NSComparisonResult result = [[NSString stringWithFormat:@"%.2f",visableRate] compare:[NSString stringWithFormat:@"%.2f",self.exposureData.config.areaRate]]; + + if (result != NSOrderedAscending) { + [self.timer start]; + } else { + [self.timer stop]; + } +} + +- (UIWindow *)topWindow { + NSArray *windows; + if (@available(iOS 13.0, *)) { + __block UIWindowScene *scene = nil; + [[UIApplication sharedApplication].connectedScenes.allObjects enumerateObjectsUsingBlock:^(UIScene * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if ([obj isKindOfClass:[UIWindowScene class]]) { + scene = (UIWindowScene *)obj; + *stop = YES; + } + }]; + windows = scene.windows; + } else { + windows = UIApplication.sharedApplication.windows; + } + + if (!windows || windows.count < 1) { + return nil; + } + + NSArray *sortedWindows = [windows sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { + UIWindow *window1 = obj1; + UIWindow *window2 = obj2; + if (window1.windowLevel < window2.windowLevel) { + return NSOrderedAscending; + } else if (window1.windowLevel == window2.windowLevel) { + return NSOrderedSame; + } else { + return NSOrderedDescending; + } + }]; + return sortedWindows.lastObject; +} + +- (void)triggerExposure { + self.state = SAExposureViewStateExposing; + self.lastExposure = [[NSDate date] timeIntervalSince1970]; + [self.timer stop]; + //track event + if (self.view == nil) { + return; + } + if ([self.view isKindOfClass:[UITableViewCell class]] || [self.view isKindOfClass:[UICollectionViewCell class]]) { + [self trackEventWithScrollView:self.scrollView cell:self.view atIndexPath:self.indexPath]; + } else { + [self trackEventWithView:self.view properties:nil]; + } +} + +- (void)trackEventWithView:(UIView *)view properties:(NSDictionary *)properties { + if (view == nil) { + return; + } + NSMutableDictionary *eventProperties = [[NSMutableDictionary alloc]init]; + [eventProperties addEntriesFromDictionary:[SAUIProperties propertiesWithView:view viewController:self.viewController]]; + if ([SAValidator isValidDictionary:properties]) { + [eventProperties addEntriesFromDictionary:properties]; + } + [eventProperties addEntriesFromDictionary:self.exposureData.properties]; + NSString *elementPath = [SAUIProperties elementPathForView:view atViewController:self.viewController]; + eventProperties[kSAEventPropertyElementPath] = elementPath; + [[SensorsAnalyticsSDK sharedInstance] track:self.exposureData.event withProperties:eventProperties]; +} + +- (void)trackEventWithScrollView:(UIScrollView *)scrollView cell:(UIView *)cell atIndexPath:(NSIndexPath *)indexPath { + NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:[SAUIProperties propertiesWithScrollView:scrollView cell:cell]]; + if (!properties) { + return; + } + NSDictionary *dic = [SAUIProperties propertiesWithAutoTrackDelegate:scrollView andIndexPath:indexPath]; + [properties addEntriesFromDictionary:dic]; + [self trackEventWithView:cell properties:properties]; +} + +- (void)setScrollView:(UIScrollView *)scrollView { + if (_scrollView == scrollView) { + return; + } + @try { + [self removeExposureScrollViewObserver]; + if (scrollView.sensorsdata_exposure_observer == self && scrollView.observationInfo) { + [scrollView removeObserver:self forKeyPath:@"contentOffset" context:kSAExposureViewContentOffsetContext]; + } + [scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kSAExposureViewContentOffsetContext]; + scrollView.sensorsdata_exposure_observer = self; + } @catch (NSException *exception) { + SALogError(@"%@", exception); + } @finally { + _scrollView = scrollView; + } +} + +- (void)setView:(UIView *)view { + if (_view == view) { + return; + } + [self removeExposureViewObserver]; + _view = view; + [self addExposureViewObserver]; +} + +- (UIViewController *)viewController { + if (self.scrollView) { + return self.scrollView.sensorsdata_viewController; + } + return self.view.sensorsdata_viewController; +} + +-(void)dealloc { + [self removeExposureObserver]; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.h b/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.h new file mode 100644 index 00000000..f1eb06e2 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.h @@ -0,0 +1,33 @@ +// +// SensorsAnalyticsSDK+Exposure.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SensorsAnalyticsSDK.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SensorsAnalyticsSDK (Exposure) + +- (void)addExposureView:(UIView *)view withData:(SAExposureData *)data; +- (void)removeExposureView:(UIView *)view withExposureIdentifier:(nullable NSString *)identifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.m b/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.m new file mode 100644 index 00000000..08cd4ee6 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/SensorsAnalyticsSDK+Exposure.m @@ -0,0 +1,38 @@ +// +// SensorsAnalyticsSDK+Exposure.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/9. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SensorsAnalyticsSDK+Exposure.h" +#import "SAExposureManager.h" + +@implementation SensorsAnalyticsSDK (Exposure) + +- (void)addExposureView:(UIView *)view withData:(SAExposureData *)data { + [[SAExposureManager defaultManager] addExposureView:view withData:data]; +} + +- (void)removeExposureView:(UIView *)view withExposureIdentifier:(NSString *)identifier { + [[SAExposureManager defaultManager] removeExposureView:view withExposureIdentifier:identifier]; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.h b/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.h new file mode 100644 index 00000000..5f751037 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.h @@ -0,0 +1,37 @@ +// +// UIScrollView+ExposureListener.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/15. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UITableView (SAExposureListener) + +- (void)sensorsdata_exposure_setDelegate:(id )delegate; + +@end + +@interface UICollectionView (SAExposureListener) + +- (void)sensorsdata_exposure_setDelegate:(id )delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.m b/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.m new file mode 100644 index 00000000..5a48066b --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIScrollView+ExposureListener.m @@ -0,0 +1,63 @@ +// +// UIScrollView+ExposureListener.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/15. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIScrollView+ExposureListener.h" +#import "SAExposureDelegateProxy.h" + +@implementation UITableView (SAExposureListener) + +- (void)sensorsdata_exposure_setDelegate:(id )delegate { + //resolve optional selectors + [SAExposureDelegateProxy resolveOptionalSelectorsForDelegate:delegate]; + + [self sensorsdata_exposure_setDelegate:delegate]; + + if (!delegate || !self.delegate) { + return; + } + + // 使用委托类去 hook 点击事件方法 + [SAExposureDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"tableView:willDisplayCell:forRowAtIndexPath:", @"tableView:didEndDisplayingCell:forRowAtIndexPath:"]]]; +} + +@end + + +@implementation UICollectionView (SAExposureListener) + +- (void)sensorsdata_exposure_setDelegate:(id )delegate { + //resolve optional selectors + [SAExposureDelegateProxy resolveOptionalSelectorsForDelegate:delegate]; + + [self sensorsdata_exposure_setDelegate:delegate]; + + if (!delegate || !self.delegate) { + return; + } + + // 使用委托类去 hook 点击事件方法 + [SAExposureDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"collectionView:willDisplayCell:forItemAtIndexPath:", @"collectionView:didEndDisplayingCell:forItemAtIndexPath:"]]]; +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.h b/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.h new file mode 100644 index 00000000..3778fd42 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.h @@ -0,0 +1,31 @@ +// +// UIView+ExposureIdentifier.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/22. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAExposureIdentifier) + +@property (nonatomic, copy, nullable) NSString *exposureIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.m b/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.m new file mode 100644 index 00000000..4a919ef4 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIView+ExposureIdentifier.m @@ -0,0 +1,42 @@ +// +// UIView+ExposureIdentifier.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/22. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+ExposureIdentifier.h" +#import + +static void *const kSAUIViewExposureIdentifierKey = (void *)&kSAUIViewExposureIdentifierKey; + +@implementation UIView (SAExposureIdentifier) + +- (NSString *)exposureIdentifier { + return objc_getAssociatedObject(self, kSAUIViewExposureIdentifierKey); +} + +- (void)setExposureIdentifier:(NSString *)exposureIdentifier { + objc_setAssociatedObject(self, kSAUIViewExposureIdentifierKey, exposureIdentifier, OBJC_ASSOCIATION_COPY); +} + + + +@end diff --git a/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.h b/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.h new file mode 100644 index 00000000..a2f80b0a --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.h @@ -0,0 +1,36 @@ +// +// UIView+ExposureListener.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAExposureListener) + +- (void)sensorsdata_didMoveToSuperview; + +/// exposure mark to improve performance on some APIs, such as didMoveToWindow +@property (nonatomic, copy, nullable) NSString *sensorsdata_exposureMark; + +@property (nonatomic, weak, nullable) NSObject *sensorsdata_exposure_observer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.m b/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.m new file mode 100644 index 00000000..baac87fd --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIView+ExposureListener.m @@ -0,0 +1,60 @@ +// +// UIView+ExposureListener.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+ExposureListener.h" +#import "UIView+ExposureIdentifier.h" +#import "SAExposureManager.h" +#import + +static void *const kSAUIViewExposureMarkKey = (void *)&kSAUIViewExposureMarkKey; +static void *const kSAUIViewExposureObserverKey = (void *)&kSAUIViewExposureObserverKey; + +@implementation UIView (SAExposureListener) + +- (void)sensorsdata_didMoveToSuperview { + [self sensorsdata_didMoveToSuperview]; + SAExposureViewObject *exposureViewObject = [[SAExposureManager defaultManager] exposureViewWithView:self]; + if (!exposureViewObject) { + return; + } + [exposureViewObject exposureConditionCheck]; +} + +- (NSString *)sensorsdata_exposureMark { + return objc_getAssociatedObject(self, kSAUIViewExposureMarkKey); +} + +- (void)setSensorsdata_exposureMark:(NSString *)sensorsdata_exposureMark { + objc_setAssociatedObject(self, kSAUIViewExposureMarkKey, sensorsdata_exposureMark, OBJC_ASSOCIATION_COPY); +} + +- (NSObject *)sensorsdata_exposure_observer { + return objc_getAssociatedObject(self, kSAUIViewExposureObserverKey); +} + +- (void)setSensorsdata_exposure_observer:(NSObject *)sensorsdata_exposure_observer { + objc_setAssociatedObject(self, kSAUIViewExposureObserverKey, sensorsdata_exposure_observer, OBJC_ASSOCIATION_RETAIN); +} + +@end diff --git a/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.h b/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.h new file mode 100644 index 00000000..852f5643 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.h @@ -0,0 +1,32 @@ +// +// UIViewController+ExposureListener.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIViewController (SAExposureListener) + +-(void)sensorsdata_exposure_viewDidAppear:(BOOL)animated; +-(void)sensorsdata_exposure_viewDidDisappear:(BOOL)animated; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.m b/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.m new file mode 100644 index 00000000..7d164ee1 --- /dev/null +++ b/SensorsAnalyticsSDK/Exposure/UIViewController+ExposureListener.m @@ -0,0 +1,53 @@ +// +// UIViewController+ExposureListener.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/10. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIViewController+ExposureListener.h" +#import "SAExposureViewObject.h" +#import "SAExposureManager.h" + +@implementation UIViewController (SAExposureListener) + +- (void)sensorsdata_exposure_viewDidAppear:(BOOL)animated { + [self sensorsdata_exposure_viewDidAppear:animated]; + + for (SAExposureViewObject *exposureViewObject in [SAExposureManager defaultManager].exposureViewObjects) { + if (exposureViewObject.viewController == self) { + [exposureViewObject exposureConditionCheck]; + } + } +} + +-(void)sensorsdata_exposure_viewDidDisappear:(BOOL)animated { + [self sensorsdata_exposure_viewDidDisappear:animated]; + + for (SAExposureViewObject *exposureViewObject in [SAExposureManager defaultManager].exposureViewObjects) { + if (exposureViewObject.viewController == self) { + exposureViewObject.state = SAExposureViewStateInvisible; + exposureViewObject.lastExposure = 0; + [exposureViewObject.timer stop]; + } + } +} + +@end diff --git a/SensorsAnalyticsSDK/SensorsAnalyticsSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SensorsAnalyticsSDK/SensorsAnalyticsSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..94b2795e --- /dev/null +++ b/SensorsAnalyticsSDK/SensorsAnalyticsSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/SensorsAnalyticsSDK/UIRelated/SAUIInternalProperties.h b/SensorsAnalyticsSDK/UIRelated/SAUIInternalProperties.h new file mode 100644 index 00000000..364e174c --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/SAUIInternalProperties.h @@ -0,0 +1,34 @@ +// +// SAUIViewInternalProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@protocol SAUIViewControllerInternalProperties + +@property (nonatomic, copy, readonly) NSString *sensorsdata_screenName; +@property (nonatomic, copy, readonly) NSString *sensorsdata_title; + +@end + +@protocol SAUIViewInternalProperties + +@property (nonatomic, weak, readonly) UIViewController *sensorsdata_viewController; + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/SAUIProperties.h b/SensorsAnalyticsSDK/UIRelated/SAUIProperties.h new file mode 100644 index 00000000..cc442141 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/SAUIProperties.h @@ -0,0 +1,52 @@ +// +// SAUIProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SAUIProperties : NSObject + ++ (NSInteger)indexWithResponder:(UIResponder *)responder; + ++ (BOOL)isIgnoredItemPathWithView:(UIView *)view; + ++ (NSString *)elementPathForView:(UIView *)view atViewController:(UIViewController *)viewController; + ++ (nullable UIViewController *)findNextViewControllerByResponder:(UIResponder *)responder; + ++ (UIViewController *)currentViewController; + ++ (NSDictionary *)propertiesWithView:(UIView *)view viewController:(UIViewController *)viewController; + ++ (NSDictionary *)propertiesWithScrollView:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath; + ++ (NSDictionary *)propertiesWithScrollView:(UIScrollView *)scrollView cell:(UIView *)cell; + ++ (NSDictionary *)propertiesWithViewController:(UIViewController *)viewController; + ++ (NSDictionary *)propertiesWithAutoTrackDelegate:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath; + ++ (UIView *)cellWithScrollView:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/SAUIProperties.m b/SensorsAnalyticsSDK/UIRelated/SAUIProperties.m new file mode 100644 index 00000000..ee9c6df4 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/SAUIProperties.m @@ -0,0 +1,346 @@ +// +// SAUIProperties.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "SAUIProperties.h" +#import "UIView+SAItemPath.h" +#import "UIView+SASimilarPath.h" +#import "UIAlertController+SASimilarPath.h" +#import "SACommonUtility.h" +#import "SAConstants+Private.h" +#import "UIView+SAElementID.h" +#import "UIView+SAElementType.h" +#import "UIView+SAElementContent.h" +#import "UIView+SAElementPosition.h" +#import "UIView+SAInternalProperties.h" +#import "UIView+SensorsAnalytics.h" +#import "UIViewController+SAInternalProperties.h" +#import "SAValidator.h" +#import "SAModuleManager.h" +#import "UIView+SensorsAnalytics.h" +#import "SALog.h" + +@implementation SAUIProperties + ++ (NSInteger)indexWithResponder:(UIResponder *)responder { + NSString *classString = NSStringFromClass(responder.class); + NSInteger index = -1; + NSArray *brothersResponder = [self siblingElementsOfResponder:responder]; + + for (UIResponder *res in brothersResponder) { + if ([classString isEqualToString:NSStringFromClass(res.class)]) { + index ++; + } + if (res == responder) { + break; + } + } + + /* 序号说明 + -1:nextResponder 不是父视图或同类元素,比如 controller.view,涉及路径不带序号 + >=0:元素序号 + */ + return index; +} + +/// 寻找所有兄弟元素 ++ (NSArray *)siblingElementsOfResponder:(UIResponder *)responder { + if ([responder isKindOfClass:UIView.class]) { + UIResponder *next = [responder nextResponder]; + if ([next isKindOfClass:UIView.class]) { + NSArray *subViews = [(UIView *)next subviews]; + if ([next isKindOfClass:UISegmentedControl.class]) { + // UISegmentedControl 点击之后,subviews 顺序会变化,需要根据坐标排序才能得到准确序号 + NSArray *brothers = [subViews sortedArrayUsingComparator:^NSComparisonResult (UIView *obj1, UIView *obj2) { + if (obj1.frame.origin.x > obj2.frame.origin.x) { + return NSOrderedDescending; + } else { + return NSOrderedAscending; + } + }]; + return brothers; + } + return subViews; + } + } else if ([responder isKindOfClass:UIViewController.class]) { + return [(UIViewController *)responder parentViewController].childViewControllers; + } + return nil; +} + ++ (BOOL)isIgnoredItemPathWithView:(UIView *)view { + NSString *className = NSStringFromClass(view.class); + /* 类名黑名单,忽略元素相对路径 + 为了兼容不同系统、不同状态下的路径匹配,忽略区分元素的路径 + */ + NSArray *ignoredItemClassNames = @[@"UITableViewWrapperView", @"UISegment", @"_UISearchBarFieldEditor", @"UIFieldEditor"]; + return [ignoredItemClassNames containsObject:className]; +} + ++ (NSString *)elementPathForView:(UIView *)view atViewController:(UIViewController *)viewController { + NSMutableArray *viewPathArray = [NSMutableArray array]; + BOOL isContainSimilarPath = NO; + + do { + if (isContainSimilarPath) { // 防止 cell 等列表嵌套,被拼上多个 [-] + if (view.sensorsdata_itemPath) { + [viewPathArray addObject:view.sensorsdata_itemPath]; + } + } else { + NSString *currentSimilarPath = view.sensorsdata_similarPath; + if (currentSimilarPath) { + [viewPathArray addObject:currentSimilarPath]; + if ([currentSimilarPath containsString:@"[-]"]) { + isContainSimilarPath = YES; + } + } + } + } while ((view = (id)view.nextResponder) && [view isKindOfClass:UIView.class]); + + if ([view isKindOfClass:UIAlertController.class]) { + UIAlertController *viewController = (UIAlertController *)view; + [viewPathArray addObject:viewController.sensorsdata_similarPath]; + } + + NSString *viewPath = [[[viewPathArray reverseObjectEnumerator] allObjects] componentsJoinedByString:@"/"]; + + return viewPath; +} + ++ (UIViewController *)findNextViewControllerByResponder:(UIResponder *)responder { + UIResponder *next = responder; + do { + if (![next isKindOfClass:UIViewController.class]) { + continue; + } + UIViewController *vc = (UIViewController *)next; + if ([vc isKindOfClass:UINavigationController.class]) { + return [self findNextViewControllerByResponder:[(UINavigationController *)vc topViewController]]; + } else if ([vc isKindOfClass:UITabBarController.class]) { + return [self findNextViewControllerByResponder:[(UITabBarController *)vc selectedViewController]]; + } + + UIViewController *parentVC = vc.parentViewController; + if (!parentVC) { + break; + } + if ([parentVC isKindOfClass:UINavigationController.class] || + [parentVC isKindOfClass:UITabBarController.class] || + [parentVC isKindOfClass:UIPageViewController.class] || + [parentVC isKindOfClass:UISplitViewController.class]) { + break; + } + } while ((next = next.nextResponder)); + return [next isKindOfClass:UIViewController.class] ? (UIViewController *)next : nil; +} + ++ (UIViewController *)currentViewController { + __block UIViewController *currentViewController = nil; + void (^ block)(void) = ^{ + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + currentViewController = [SAUIProperties findCurrentViewControllerFromRootViewController:rootViewController isRoot:YES]; + }; + + [SACommonUtility performBlockOnMainThread:block]; + return currentViewController; +} + ++ (UIViewController *)findCurrentViewControllerFromRootViewController:(UIViewController *)viewController isRoot:(BOOL)isRoot { + if ([self canFindPresentedViewController:viewController.presentedViewController]) { + return [self findCurrentViewControllerFromRootViewController:viewController.presentedViewController isRoot:NO]; + } + + if ([viewController isKindOfClass:[UITabBarController class]]) { + return [self findCurrentViewControllerFromRootViewController:[(UITabBarController *)viewController selectedViewController] isRoot:NO]; + } + + if ([viewController isKindOfClass:[UINavigationController class]]) { + // 根视图为 UINavigationController + UIViewController *topViewController = [(UINavigationController *)viewController topViewController]; + return [self findCurrentViewControllerFromRootViewController:topViewController isRoot:NO]; + } + + if (viewController.childViewControllers.count > 0) { + if (viewController.childViewControllers.count == 1 && isRoot) { + return [self findCurrentViewControllerFromRootViewController:viewController.childViewControllers.firstObject isRoot:NO]; + } else { + __block UIViewController *currentViewController = viewController; + //从最上层遍历(逆序),查找正在显示的 UITabBarController 或 UINavigationController 类型的 + // 是否包含 UINavigationController 或 UITabBarController 类全屏显示的 controller + [viewController.childViewControllers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIViewController *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + // 判断 obj.view 是否加载,如果尚未加载,调用 obj.view 会触发 viewDidLoad,可能影响客户业务 + if (obj.isViewLoaded) { + CGPoint point = [obj.view convertPoint:CGPointZero toView:nil]; + CGSize windowSize = obj.view.window.bounds.size; + // 正在全屏显示 + BOOL isFullScreenShow = !obj.view.hidden && obj.view.alpha > 0.01 && CGPointEqualToPoint(point, CGPointZero) && CGSizeEqualToSize(obj.view.bounds.size, windowSize); + // 判断类型 + BOOL isStopFindController = [obj isKindOfClass:UINavigationController.class] || [obj isKindOfClass:UITabBarController.class]; + if (isFullScreenShow && isStopFindController) { + currentViewController = [self findCurrentViewControllerFromRootViewController:obj isRoot:NO]; + *stop = YES; + } + } + }]; + return currentViewController; + } + } else if ([viewController respondsToSelector:NSSelectorFromString(@"contentViewController")]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + UIViewController *tempViewController = [viewController performSelector:NSSelectorFromString(@"contentViewController")]; +#pragma clang diagnostic pop + if (tempViewController) { + return [self findCurrentViewControllerFromRootViewController:tempViewController isRoot:NO]; + } + } + return viewController; +} + ++ (BOOL)canFindPresentedViewController:(UIViewController *)viewController { + if (!viewController) { + return NO; + } + if ([viewController isKindOfClass:UIAlertController.class]) { + return NO; + } + if ([@"_UIContextMenuActionsOnlyViewController" isEqualToString:NSStringFromClass(viewController.class)]) { + return NO; + } + return YES; +} + ++ (NSDictionary *)propertiesWithView:(UIView *)view viewController:(UIViewController *)viewController { + NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; + + viewController = viewController ? : view.sensorsdata_viewController; + NSDictionary *dic = [self propertiesWithViewController:viewController]; + [properties addEntriesFromDictionary:dic]; + + properties[kSAEventPropertyElementId] = view.sensorsdata_elementId; + properties[kSAEventPropertyElementType] = view.sensorsdata_elementType; + properties[kSAEventPropertyElementContent] = view.sensorsdata_elementContent; + properties[kSAEventPropertyElementPosition] = view.sensorsdata_elementPosition; + [properties addEntriesFromDictionary:view.sensorsAnalyticsViewProperties]; + + // viewPath + NSDictionary *viewPathProperties = [[SAModuleManager sharedInstance] propertiesWithView:view]; + if (viewPathProperties) { + [properties addEntriesFromDictionary:viewPathProperties]; + } + return properties; +} + ++ (NSDictionary *)propertiesWithScrollView:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath { + UIView *cell = [self cellWithScrollView:scrollView andIndexPath:indexPath]; + return [self propertiesWithScrollView:scrollView cell:cell]; +} + ++ (NSDictionary *)propertiesWithScrollView:(UIScrollView *)scrollView cell:(UIView *)cell { + if (!cell) { + return nil; + } + NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; + UIViewController *viewController = scrollView.sensorsdata_viewController; + NSDictionary *dic = [self propertiesWithViewController:viewController]; + [properties addEntriesFromDictionary:dic]; + + properties[kSAEventPropertyElementId] = scrollView.sensorsdata_elementId; + properties[kSAEventPropertyElementType] = scrollView.sensorsdata_elementType; + properties[kSAEventPropertyElementContent] = cell.sensorsdata_elementContent; + properties[kSAEventPropertyElementPosition] = cell.sensorsdata_elementPosition; + + //View Properties + NSDictionary *viewProperties = scrollView.sensorsAnalyticsViewProperties; + if (viewProperties.count > 0) { + [properties addEntriesFromDictionary:viewProperties]; + } + + // viewPath + NSDictionary *viewPathProperties = [[SAModuleManager sharedInstance] propertiesWithView:cell]; + if (viewPathProperties) { + [properties addEntriesFromDictionary:viewPathProperties]; + } + return [properties copy]; +} + ++ (NSDictionary *)propertiesWithViewController:(UIViewController *)viewController { + NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; + properties[kSAEventPropertyScreenName] = viewController.sensorsdata_screenName; + properties[kSAEventPropertyTitle] = viewController.sensorsdata_title; + + SEL getTrackProperties = NSSelectorFromString(@"getTrackProperties"); + if ([viewController respondsToSelector:getTrackProperties]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + NSDictionary *trackProperties = [viewController performSelector:getTrackProperties]; +#pragma clang diagnostic pop + if ([SAValidator isValidDictionary:trackProperties]) { + [properties addEntriesFromDictionary:trackProperties]; + } + } + return [properties copy]; +} + ++ (UIView *)cellWithScrollView:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath { + UIView *cell = nil; + if ([scrollView isKindOfClass:UITableView.class]) { + UITableView *tableView = (UITableView *)scrollView; + cell = [tableView cellForRowAtIndexPath:indexPath]; + if (!cell) { + [tableView layoutIfNeeded]; + cell = [tableView cellForRowAtIndexPath:indexPath]; + } + } else if ([scrollView isKindOfClass:UICollectionView.class]) { + UICollectionView *collectionView = (UICollectionView *)scrollView; + cell = [collectionView cellForItemAtIndexPath:indexPath]; + if (!cell) { + [collectionView layoutIfNeeded]; + cell = [collectionView cellForItemAtIndexPath:indexPath]; + } + } + return cell; +} + ++ (NSDictionary *)propertiesWithAutoTrackDelegate:(UIScrollView *)scrollView andIndexPath:(NSIndexPath *)indexPath { + NSDictionary *properties = nil; + @try { + if ([scrollView isKindOfClass:UITableView.class]) { + UITableView *tableView = (UITableView *)scrollView; + + if ([tableView.sensorsAnalyticsDelegate respondsToSelector:@selector(sensorsAnalytics_tableView:autoTrackPropertiesAtIndexPath:)]) { + properties = [tableView.sensorsAnalyticsDelegate sensorsAnalytics_tableView:tableView autoTrackPropertiesAtIndexPath:indexPath]; + } + } else if ([scrollView isKindOfClass:UICollectionView.class]) { + UICollectionView *collectionView = (UICollectionView *)scrollView; + if ([collectionView.sensorsAnalyticsDelegate respondsToSelector:@selector(sensorsAnalytics_collectionView:autoTrackPropertiesAtIndexPath:)]) { + properties = [collectionView.sensorsAnalyticsDelegate sensorsAnalytics_collectionView:collectionView autoTrackPropertiesAtIndexPath:indexPath]; + } + } + } @catch (NSException *exception) { + SALogError(@"%@ error: %@", self, exception); + } + NSAssert(!properties || [properties isKindOfClass:[NSDictionary class]], @"You must return a dictionary object ❌"); + return properties; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/SAUIViewElementProperties.h b/SensorsAnalyticsSDK/UIRelated/SAUIViewElementProperties.h new file mode 100644 index 00000000..477c6824 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/SAUIViewElementProperties.h @@ -0,0 +1,35 @@ +// +// SAUIViewElementProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SAUIViewElementProperties + +@optional +@property (nonatomic, copy, readonly) NSString *sensorsdata_elementType; +@property (nonatomic, copy, readonly) NSString *sensorsdata_elementContent; +@property (nonatomic, copy, readonly) NSString *sensorsdata_elementId; +@property (nonatomic, copy, readonly) NSString *sensorsdata_elementPosition; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/SAUIViewPathProperties.h b/SensorsAnalyticsSDK/UIRelated/SAUIViewPathProperties.h new file mode 100644 index 00000000..430316c0 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/SAUIViewPathProperties.h @@ -0,0 +1,35 @@ +// +// SAUIViewPathProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SAUIViewPathProperties + +@optional +@property (nonatomic, copy, readonly) NSString *sensorsdata_itemPath; +@property (nonatomic, copy, readonly) NSString *sensorsdata_similarPath; +@property (nonatomic, copy, readonly) NSIndexPath *sensorsdata_IndexPath; +@property (nonatomic, copy, readonly) NSString *sensorsdata_elementPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.h b/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.h new file mode 100644 index 00000000..6e0d441c --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.h @@ -0,0 +1,31 @@ +// +// UIAlertController+SASimilarPath.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewPathProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIAlertController (SASimilarPath) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.m b/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.m new file mode 100644 index 00000000..9667b58f --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIAlertController+SASimilarPath.m @@ -0,0 +1,39 @@ +// +// UIAlertController+SASimilarPath.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIAlertController+SASimilarPath.h" +#import "SAUIProperties.h" + +@implementation UIAlertController (SASimilarPath) + +- (NSString *)sensorsdata_similarPath { + NSString *className = NSStringFromClass(self.class); + NSInteger index = [SAUIProperties indexWithResponder:self]; + if (index < 0) { // -1 + return className; + } + return [NSString stringWithFormat:@"%@[%ld]", className, (long)index]; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.h b/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.h new file mode 100644 index 00000000..d40aa391 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.h @@ -0,0 +1,41 @@ +// +// UIScrollView+SADelegateHashTable.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/9/3. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UITableView (SADelegateHashTable) + +@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_delegateHashTable; + +@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_exposure_delegateHashTable; + +@end + +@interface UICollectionView (SADelegateHashTable) + +@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_delegateHashTable; + +@property (nonatomic, strong, nullable) NSHashTable *sensorsdata_exposure_delegateHashTable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.m b/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.m new file mode 100644 index 00000000..9556a2da --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIScrollView+SADelegateHashTable.m @@ -0,0 +1,92 @@ +// +// UIScrollView+SADelegateHashTable.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/9/3. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIScrollView+SADelegateHashTable.h" +#import + +static const void *kSATableViewDelegateHashTable = &kSATableViewDelegateHashTable; +static const void *kSACollectionViewDelegateHashTable = &kSACollectionViewDelegateHashTable; + +static const void *kSATableViewExposureDelegateHashTable = &kSATableViewExposureDelegateHashTable; +static const void *kSACollectionViewExposureDelegateHashTable = &kSACollectionViewExposureDelegateHashTable; + +@implementation UITableView (SADelegateHashTable) + +- (void)setSensorsdata_delegateHashTable:(NSHashTable *)delegateHashTable { + objc_setAssociatedObject(self, kSATableViewDelegateHashTable, delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSHashTable *)sensorsdata_delegateHashTable { + NSHashTable *delegateHashTable = objc_getAssociatedObject(self, kSATableViewDelegateHashTable); + if (!delegateHashTable) { + delegateHashTable = [NSHashTable weakObjectsHashTable]; + self.sensorsdata_delegateHashTable = delegateHashTable; + } + return delegateHashTable; +} + +- (void)setSensorsdata_exposure_delegateHashTable:(NSHashTable *)sensorsdata_exposure_delegateHashTable { + objc_setAssociatedObject(self, kSATableViewExposureDelegateHashTable, sensorsdata_exposure_delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSHashTable *)sensorsdata_exposure_delegateHashTable { + NSHashTable *exposureDelegateHashTable = objc_getAssociatedObject(self, kSATableViewExposureDelegateHashTable); + if (!exposureDelegateHashTable) { + exposureDelegateHashTable = [NSHashTable weakObjectsHashTable]; + self.sensorsdata_exposure_delegateHashTable = exposureDelegateHashTable; + } + return exposureDelegateHashTable; +} + +@end + +@implementation UICollectionView (SADelegateHashTable) + +- (void)setSensorsdata_delegateHashTable:(NSHashTable *)delegateHashTable { + objc_setAssociatedObject(self, kSACollectionViewDelegateHashTable, delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSHashTable *)sensorsdata_delegateHashTable { + NSHashTable *delegateHashTable = objc_getAssociatedObject(self, kSACollectionViewDelegateHashTable); + if (!delegateHashTable) { + delegateHashTable = [NSHashTable weakObjectsHashTable]; + self.sensorsdata_delegateHashTable = delegateHashTable; + } + return delegateHashTable; +} + +- (void)setSensorsdata_exposure_delegateHashTable:(NSHashTable *)sensorsdata_exposure_delegateHashTable { + objc_setAssociatedObject(self, kSACollectionViewExposureDelegateHashTable, sensorsdata_exposure_delegateHashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSHashTable *)sensorsdata_exposure_delegateHashTable { + NSHashTable *exposureDelegateHashTable = objc_getAssociatedObject(self, kSACollectionViewExposureDelegateHashTable); + if (!exposureDelegateHashTable) { + exposureDelegateHashTable = [NSHashTable weakObjectsHashTable]; + self.sensorsdata_exposure_delegateHashTable = exposureDelegateHashTable; + } + return exposureDelegateHashTable; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.h b/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.h new file mode 100644 index 00000000..c70e4fc3 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.h @@ -0,0 +1,35 @@ +// +// UITableViewCell+SAIndexPath.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewPathProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UITableViewCell (SAIndexPath) + +@end + +@interface UICollectionViewCell (SAIndexPath) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.m b/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.m new file mode 100644 index 00000000..6814e999 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UITableViewCell+SAIndexPath.m @@ -0,0 +1,53 @@ +// +// UITableViewCell+SAIndexPath.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UITableViewCell+SAIndexPath.h" + +@implementation UITableViewCell (SAIndexPath) + +- (NSIndexPath *)sensorsdata_IndexPath { + UITableView *tableView = (UITableView *)[self superview]; + do { + if ([tableView isKindOfClass:UITableView.class]) { + NSIndexPath *indexPath = [tableView indexPathForCell:self]; + return indexPath; + } + } while ((tableView = (UITableView *)[tableView superview])); + return nil; +} + +@end + +@implementation UICollectionViewCell (SAIndexPath) + +- (NSIndexPath *)sensorsdata_IndexPath { + UICollectionView *collectionView = (UICollectionView *)[self superview]; + if ([collectionView isKindOfClass:UICollectionView.class]) { + NSIndexPath *indexPath = [collectionView indexPathForCell:self]; + return indexPath; + } + return nil; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.h b/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.h new file mode 100644 index 00000000..70ce5bef --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.h @@ -0,0 +1,60 @@ +// +// UIVIew+SensorsAnalytics.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SAUIViewAutoTrackDelegate + +//UITableView +@optional +- (NSDictionary *)sensorsAnalytics_tableView:(UITableView *)tableView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath; + +//UICollectionView +@optional +- (NSDictionary *)sensorsAnalytics_collectionView:(UICollectionView *)collectionView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath; +@end + +@interface UIView (SensorsAnalytics) + +/// viewID +@property (nonatomic, copy) NSString *sensorsAnalyticsViewID; + +/// AutoTrack 时,是否忽略该 View +@property (nonatomic, assign) BOOL sensorsAnalyticsIgnoreView; + +/// AutoTrack 发生在 SendAction 之前还是之后,默认是 SendAction 之前 +@property (nonatomic, assign) BOOL sensorsAnalyticsAutoTrackAfterSendAction; + +/// AutoTrack 时,View 的扩展属性 +@property (nonatomic, strong) NSDictionary *sensorsAnalyticsViewProperties; + +@property (nonatomic, weak, nullable) id sensorsAnalyticsDelegate; + +@end + +@interface UIImage (SensorsAnalytics) + +@property (nonatomic, copy) NSString* sensorsAnalyticsImageName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.m b/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.m new file mode 100644 index 00000000..e1eff814 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIVIew+SensorsAnalytics.m @@ -0,0 +1,95 @@ +// +// UIVIew+SensorsAnalytics.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SensorsAnalytics.h" +#import "SAWeakPropertyContainer.h" +#include + +static void *const kSASensorsAnalyticsViewIDKey = (void *)&kSASensorsAnalyticsViewIDKey; +static void *const kSASensorsAnalyticsIgnoreViewKey = (void *)&kSASensorsAnalyticsIgnoreViewKey; +static void *const kSASensorsAnalyticsAutoTrackAfterSendActionKey = (void *)&kSASensorsAnalyticsAutoTrackAfterSendActionKey; +static void *const kSASensorsAnalyticsViewPropertiesKey = (void *)&kSASensorsAnalyticsViewPropertiesKey; +static void *const kSASensorsAnalyticsImageNameKey = (void *)&kSASensorsAnalyticsImageNameKey; + +@implementation UIView (SensorsAnalytics) + +//viewID +- (NSString *)sensorsAnalyticsViewID { + return objc_getAssociatedObject(self, kSASensorsAnalyticsViewIDKey); +} + +- (void)setSensorsAnalyticsViewID:(NSString *)sensorsAnalyticsViewID { + objc_setAssociatedObject(self, kSASensorsAnalyticsViewIDKey, sensorsAnalyticsViewID, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +//ignoreView +- (BOOL)sensorsAnalyticsIgnoreView { + return [objc_getAssociatedObject(self, kSASensorsAnalyticsIgnoreViewKey) boolValue]; +} + +- (void)setSensorsAnalyticsIgnoreView:(BOOL)sensorsAnalyticsIgnoreView { + objc_setAssociatedObject(self, kSASensorsAnalyticsIgnoreViewKey, [NSNumber numberWithBool:sensorsAnalyticsIgnoreView], OBJC_ASSOCIATION_ASSIGN); +} + +//afterSendAction +- (BOOL)sensorsAnalyticsAutoTrackAfterSendAction { + return [objc_getAssociatedObject(self, kSASensorsAnalyticsAutoTrackAfterSendActionKey) boolValue]; +} + +- (void)setSensorsAnalyticsAutoTrackAfterSendAction:(BOOL)sensorsAnalyticsAutoTrackAfterSendAction { + objc_setAssociatedObject(self, kSASensorsAnalyticsAutoTrackAfterSendActionKey, [NSNumber numberWithBool:sensorsAnalyticsAutoTrackAfterSendAction], OBJC_ASSOCIATION_ASSIGN); +} + +//viewProperty +- (NSDictionary *)sensorsAnalyticsViewProperties { + return objc_getAssociatedObject(self, kSASensorsAnalyticsViewPropertiesKey); +} + +- (void)setSensorsAnalyticsViewProperties:(NSDictionary *)sensorsAnalyticsViewProperties { + objc_setAssociatedObject(self, kSASensorsAnalyticsViewPropertiesKey, sensorsAnalyticsViewProperties, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (id)sensorsAnalyticsDelegate { + SAWeakPropertyContainer *container = objc_getAssociatedObject(self, @"sensorsAnalyticsDelegate"); + return container.weakProperty; +} + +- (void)setSensorsAnalyticsDelegate:(id)sensorsAnalyticsDelegate { + SAWeakPropertyContainer *container = [SAWeakPropertyContainer containerWithWeakProperty:sensorsAnalyticsDelegate]; + objc_setAssociatedObject(self, @"sensorsAnalyticsDelegate", container, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end + +@implementation UIImage (SensorsAnalytics) + +- (NSString *)sensorsAnalyticsImageName { + return objc_getAssociatedObject(self, kSASensorsAnalyticsImageNameKey); +} + +- (void)setSensorsAnalyticsImageName:(NSString *)sensorsAnalyticsImageName { + objc_setAssociatedObject(self, kSASensorsAnalyticsImageNameKey, sensorsAnalyticsImageName, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.h new file mode 100644 index 00000000..a42a67ea --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.h @@ -0,0 +1,67 @@ +// +// UIView+SAElementContent.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "SAUIViewElementProperties.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAElementContent) + +@end + +@interface UILabel (SAElementContent) + +@end + +@interface UIImageView (SAElementContent) + +@end + +@interface UISearchBar (SAElementContent) + +@end + +@interface UIButton (SAElementContent) + +@end + +@interface UISwitch (SAElementContent) + +@end + +@interface UIStepper (SAElementContent) + +@end + +@interface UISegmentedControl (SAElementContent) + +@end + +@interface UIPageControl (SAElementContent) + +@end + +@interface UISlider (SAElementContent) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.m new file mode 100644 index 00000000..7061d372 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementContent.m @@ -0,0 +1,181 @@ +// +// UIView+SAElementContent.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAElementContent.h" +#import "UIVIew+SensorsAnalytics.h" + +@implementation UIView (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + if ([self isKindOfClass:NSClassFromString(@"RTLabel")]) { // RTLabel:https://github.com/honcheng/RTLabel +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + if ([self respondsToSelector:NSSelectorFromString(@"text")]) { + NSString *title = [self performSelector:NSSelectorFromString(@"text")]; + if (title.length > 0) { + return title; + } + } + return nil; + } + if ([self isKindOfClass:NSClassFromString(@"YYLabel")]) { // RTLabel:https://github.com/ibireme/YYKit + if ([self respondsToSelector:NSSelectorFromString(@"text")]) { + NSString *title = [self performSelector:NSSelectorFromString(@"text")]; + if (title.length > 0) { + return title; + } + } + return nil; +#pragma clang diagnostic pop + } + if ([self isKindOfRNView:self]) { // RN 元素,https://reactnative.dev + NSString *content = [self.accessibilityLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (content.length > 0) { + return content; + } + } + + if ([self isKindOfClass:NSClassFromString(@"WXView")]) { // WEEX 元素,http://doc.weex.io/zh/docs/components/a.html + NSString *content = [self.accessibilityValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (content.length > 0) { + return content; + } + } + + NSMutableArray *elementContentArray = [NSMutableArray array]; + for (UIView *subview in self.subviews) { + // 忽略隐藏控件 + if (subview.isHidden || subview.sensorsAnalyticsIgnoreView) { + continue; + } + NSString *temp = subview.sensorsdata_elementContent; + if (temp.length > 0) { + [elementContentArray addObject:temp]; + } + } + if (elementContentArray.count > 0) { + return [elementContentArray componentsJoinedByString:@"-"]; + } + + return nil; +} + +- (BOOL)isKindOfRNView:(UIView *)view { + NSString *className = NSStringFromClass(view.class); + if ([className isEqualToString:@"UISegment"]) { + // 针对 UISegment,可能是 RCTSegmentedControl 或 RNCSegmentedControl 内嵌元素,使用父视图判断是否为 RN 元素 + view = [view superview]; + } + NSArray *classNames = @[@"RCTSurfaceView", @"RCTSurfaceHostingView", @"RCTFPSGraph", @"RCTModalHostView", @"RCTView", @"RCTTextView", @"RCTRootView", @"RCTInputAccessoryView", @"RCTInputAccessoryViewContent", @"RNSScreenContainerView", @"RNSScreen", @"RCTVideo", @"RCTSwitch", @"RCTSlider", @"RCTSegmentedControl", @"RNGestureHandlerButton", @"RNCSlider", @"RNCSegmentedControl"]; + for (NSString *className in classNames) { + Class class = NSClassFromString(className); + if (class && [view isKindOfClass:class]) { + return YES; + } + } + return NO; +} + +@end + +@implementation UILabel (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return self.text ?: super.sensorsdata_elementContent; +} + +@end + +@implementation UIImageView (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + NSString *imageName = self.image.sensorsAnalyticsImageName; + if (imageName.length > 0) { + return [NSString stringWithFormat:@"%@", imageName]; + } + return super.sensorsdata_elementContent; +} + +@end + +@implementation UISearchBar (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return self.text; +} + +@end + +@implementation UIButton (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + NSString *text = self.titleLabel.text; + if (!text) { + text = super.sensorsdata_elementContent; + } + return text; + +} + +@end + +@implementation UISwitch (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return self.on ? @"checked" : @"unchecked"; +} + +@end + +@implementation UIStepper (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return [NSString stringWithFormat:@"%g", self.value]; +} + +@end + +@implementation UISegmentedControl (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return self.selectedSegmentIndex == UISegmentedControlNoSegment ? [super sensorsdata_elementContent] : [self titleForSegmentAtIndex:self.selectedSegmentIndex]; +} + +@end + +@implementation UIPageControl (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return [NSString stringWithFormat:@"%ld", (long)self.currentPage]; +} + +@end + +@implementation UISlider (SAElementContent) + +- (NSString *)sensorsdata_elementContent { + return [NSString stringWithFormat:@"%f", self.value]; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.h new file mode 100644 index 00000000..eece5d5b --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.h @@ -0,0 +1,31 @@ +// +// UIView+SAElementID.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewElementProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAElementID) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.m new file mode 100644 index 00000000..ef029b6c --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementID.m @@ -0,0 +1,34 @@ +// +// UIView+SAElementID.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAElementID.h" +#import "UIView+SensorsAnalytics.h" + +@implementation UIView (SAElementID) + +- (NSString *)sensorsdata_elementId { + return self.sensorsAnalyticsViewID; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.h new file mode 100644 index 00000000..755dd439 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.h @@ -0,0 +1,31 @@ +// +// UIView+SAElementPath.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewPathProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAElementPath) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.m new file mode 100644 index 00000000..b58a7966 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPath.m @@ -0,0 +1,45 @@ +// +// UIView+SAElementPath.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAElementPath.h" +#import "SAUIProperties.h" +#import "SAUIInternalProperties.h" +#import "UIView+SAInternalProperties.h" + +@implementation UIView (SAElementPath) + +- (NSString *)sensorsdata_elementPath { + // 处理特殊控件 + // UISegmentedControl 嵌套 UISegment 作为选项单元格,特殊处理 + if ([NSStringFromClass(self.class) isEqualToString:@"UISegment"]) { + UISegmentedControl *segmentedControl = (UISegmentedControl *)[self superview]; + if ([segmentedControl isKindOfClass:UISegmentedControl.class]) { + return [SAUIProperties elementPathForView:segmentedControl atViewController:segmentedControl.sensorsdata_viewController]; + } + } + // 支持自定义属性,可见元素均上传 elementPath + return [SAUIProperties elementPathForView:self atViewController:self.sensorsdata_viewController]; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.h new file mode 100644 index 00000000..f0dd2db3 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.h @@ -0,0 +1,51 @@ +// +// UIView+SAElementPosition.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "SAUIViewElementProperties.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAElementPosition) + +@end + +@interface UIImageView (SAElementPosition) + +@end + +@interface UIControl (SAElementPosition) + +@end + +@interface UISegmentedControl (SAElementPosition) + +@end + +@interface UITableViewCell (SAElementPosition) + +@end + +@interface UICollectionViewCell (SAElementPosition) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.m new file mode 100644 index 00000000..47752536 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementPosition.m @@ -0,0 +1,98 @@ +// +// UIView+SAElementPosition.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAElementPosition.h" +#import "SAUIProperties.h" +#import "UITableViewCell+SAIndexPath.h" + +@implementation UIView (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + UIView *superView = self.superview; + if (!superView) { + return nil; + } + return superView.sensorsdata_elementPosition; +} + +@end + +@implementation UIImageView (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + if ([NSStringFromClass(self.class) isEqualToString:@"UISegment"]) { + NSInteger index = [SAUIProperties indexWithResponder:self]; + return index > 0 ? [NSString stringWithFormat:@"%ld", (long)index] : @"0"; + } + return [super sensorsdata_elementPosition]; +} + +@end + +@implementation UIControl (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + if ([NSStringFromClass(self.class) isEqualToString:@"UITabBarButton"]) { + NSInteger index = [SAUIProperties indexWithResponder:self]; + if (index < 0) { + index = 0; + } + return [NSString stringWithFormat:@"%ld", (long)index]; + } + return super.sensorsdata_elementPosition; +} + +@end + +@implementation UISegmentedControl (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + return self.selectedSegmentIndex == UISegmentedControlNoSegment ? [super sensorsdata_elementPosition] : [NSString stringWithFormat: @"%ld", (long)self.selectedSegmentIndex]; +} + +@end + +@implementation UITableViewCell (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%ld:%ld", (long)indexPath.section, (long)indexPath.row]; + } + return nil; +} + +@end + +@implementation UICollectionViewCell (SAElementPosition) + +- (NSString *)sensorsdata_elementPosition { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%ld:%ld", (long)indexPath.section, (long)indexPath.item]; + } + return nil; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.h new file mode 100644 index 00000000..6c8fa88c --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.h @@ -0,0 +1,30 @@ +// +// UIView+SAElementType.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SAUIViewElementProperties.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAElementType) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.m new file mode 100644 index 00000000..5d72cb7a --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAElementType.m @@ -0,0 +1,65 @@ +// +// UIView+SAElementType.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAElementType.h" + +static NSString * const kSAMenuElementType = @"UIMenu"; + +@implementation UIView (SAElementType) + +- (NSString *)sensorsdata_elementType { + NSString *viewType = NSStringFromClass(self.class); + if ([viewType isEqualToString:@"_UIInterfaceActionCustomViewRepresentationView"] || + [viewType isEqualToString:@"_UIAlertControllerCollectionViewCell"]) { + return [self alertElementType]; + } + + // _UIContextMenuActionView 为 iOS 13 UIMenu 最终响应事件的控件类型; + // _UIContextMenuActionsListCell 为 iOS 14 UIMenu 最终响应事件的控件类型; + if ([viewType isEqualToString:@"_UIContextMenuActionView"] || + [viewType isEqualToString:@"_UIContextMenuActionsListCell"]) { + return [self menuElementType]; + } + return viewType; +} + +- (NSString *)alertElementType { + UIWindow *window = self.window; + if ([NSStringFromClass(window.class) isEqualToString:@"_UIAlertControllerShimPresenterWindow"]) { + CGFloat actionHeight = self.bounds.size.height; + if (actionHeight > 50) { + return NSStringFromClass(UIActionSheet.class); + } else { + return NSStringFromClass(UIAlertView.class); + } + } else { + return NSStringFromClass(UIAlertController.class); + } +} + +- (NSString *)menuElementType { + return kSAMenuElementType; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.h new file mode 100644 index 00000000..88c41b24 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.h @@ -0,0 +1,31 @@ +// +// UIView+SAInternalProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIInternalProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAInternalProperties) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.m new file mode 100644 index 00000000..e4809eff --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAInternalProperties.m @@ -0,0 +1,40 @@ +// +// UIView+SAInternalProperties.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAInternalProperties.h" +#import "SAUIProperties.h" + +@implementation UIView (SAInternalProperties) + +- (UIViewController *)sensorsdata_viewController { + UIViewController *viewController = [SAUIProperties findNextViewControllerByResponder:self]; + + // 获取当前 controller 作为 screen_name + if (!viewController || [viewController isKindOfClass:UIAlertController.class]) { + viewController = [SAUIProperties currentViewController]; + } + return (UIViewController *)viewController; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.h b/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.h new file mode 100644 index 00000000..eb58b33f --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.h @@ -0,0 +1,47 @@ +// +// UIView+SAItemPath.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewPathProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SAItemPath) + +@end + +@interface UISegmentedControl (SAItemPath) + +@end + +@interface UITableViewHeaderFooterView (SAItemPath) + +@end + +@interface UITableViewCell (SAItemPath) + +@end + +@interface UICollectionViewCell (SAItemPath) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.m b/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.m new file mode 100644 index 00000000..a9644534 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SAItemPath.m @@ -0,0 +1,110 @@ +// +// UIView+SAItemPath.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SAItemPath.h" +#import "SAUIProperties.h" +#import "UITableViewCell+SAIndexPath.h" + +@implementation UIView (SAItemPath) + +- (NSString *)sensorsdata_itemPath { + /* 忽略路径 + UITableViewWrapperView 为 iOS11 以下 UITableView 与 cell 之间的 view + _UITextFieldCanvasView 和 _UISearchBarFieldEditor 都是 UISearchBar 内部私有 view + 在输入状态下 ...UISearchBarTextField/_UISearchBarFieldEditor/_UITextFieldCanvasView/... + 非输入状态下 .../UISearchBarTextField/_UITextFieldCanvasView + 并且 _UITextFieldCanvasView 是个私有 view,无法获取元素内容(目前通过 nextResponder 获取 textField 采集内容)。方便路径统一,所以忽略 _UISearchBarFieldEditor 路径 + */ + if ([SAUIProperties isIgnoredItemPathWithView:self]) { + return nil; + } + + NSString *className = NSStringFromClass(self.class); + NSInteger index = [SAUIProperties indexWithResponder:self]; + if (index < 0) { // -1 + return className; + } + return [NSString stringWithFormat:@"%@[%ld]", className, (long)index]; +} + +@end + +@implementation UISegmentedControl (SAItemPath) + +- (NSString *)sensorsdata_itemPath { + // 支持单个 UISegment 创建事件。UISegment 是 UIImageView 的私有子类,表示UISegmentedControl 单个选项的显示区域 + NSString *subPath = [NSString stringWithFormat:@"UISegment[%ld]", (long)self.selectedSegmentIndex]; + return [NSString stringWithFormat:@"%@/%@", super.sensorsdata_itemPath, subPath]; +} + +@end + +@implementation UITableViewHeaderFooterView (SAItemPath) + +- (NSString *)sensorsdata_itemPath { + UITableView *tableView = (UITableView *)self.superview; + + while (![tableView isKindOfClass:UITableView.class]) { + tableView = (UITableView *)tableView.superview; + if (!tableView) { + return super.sensorsdata_itemPath; + } + } + for (NSInteger i = 0; i < tableView.numberOfSections; i++) { + if (self == [tableView headerViewForSection:i]) { + return [NSString stringWithFormat:@"[SectionHeader][%ld]", (long)i]; + } + if (self == [tableView footerViewForSection:i]) { + return [NSString stringWithFormat:@"[SectionFooter][%ld]", (long)i]; + } + } + return super.sensorsdata_itemPath; +} + +@end + +@implementation UITableViewCell (SAItemPath) + +- (NSString *)sensorsdata_itemPath { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%@[%ld][%ld]", NSStringFromClass(self.class), (long)indexPath.section, (long)indexPath.row]; + } + return [super sensorsdata_itemPath]; +} + +@end + +@implementation UICollectionViewCell (SAItemPath) + +- (NSString *)sensorsdata_itemPath { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%@[%ld][%ld]", NSStringFromClass(self.class), (long)indexPath.section, (long)indexPath.item]; + } + return [super sensorsdata_itemPath]; +} + +@end + diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.h b/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.h new file mode 100644 index 00000000..647f4834 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.h @@ -0,0 +1,32 @@ +// +// UIView+SARNView.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/31. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SARNView) + +- (BOOL)isSensorsdataRNView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.m b/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.m new file mode 100644 index 00000000..aabdfc0d --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SARNView.m @@ -0,0 +1,46 @@ +// +// UIView+SARNView.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/31. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SARNView.h" + +@implementation UIView (SARNView) + +- (BOOL)isSensorsdataRNView { + UIView *view = self; + NSString *className = NSStringFromClass(view.class); + if ([className isEqualToString:@"UISegment"]) { + // 针对 UISegment,可能是 RCTSegmentedControl 或 RNCSegmentedControl 内嵌元素,使用父视图判断是否为 RN 元素 + view = [view superview]; + } + NSArray *classNames = @[@"RCTSurfaceView", @"RCTSurfaceHostingView", @"RCTFPSGraph", @"RCTModalHostView", @"RCTView", @"RCTTextView", @"RCTRootView", @"RCTInputAccessoryView", @"RCTInputAccessoryViewContent", @"RNSScreenContainerView", @"RNSScreen", @"RCTVideo", @"RCTSwitch", @"RCTSlider", @"RCTSegmentedControl", @"RNGestureHandlerButton", @"RNCSlider", @"RNCSegmentedControl"]; + for (NSString *className in classNames) { + Class class = NSClassFromString(className); + if (class && [view isKindOfClass:class]) { + return YES; + } + } + return NO; +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.h b/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.h new file mode 100644 index 00000000..510f1604 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.h @@ -0,0 +1,43 @@ +// +// UIView+SASimilarPath.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIViewPathProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SASimilarPath) + +@end + +@interface UISegmentedControl (SASimilarPath) + +@end + +@interface UITableViewCell (SASimilarPath) + +@end + +@interface UICollectionViewCell (SASimilarPath) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.m b/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.m new file mode 100644 index 00000000..a1bf9cba --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIView+SASimilarPath.m @@ -0,0 +1,75 @@ +// +// UIView+SASimilarPath.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/29. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIView+SASimilarPath.h" +#import "UIView+SAElementPosition.h" +#import "UIView+SAItemPath.h" +#import "UITableViewCell+SAIndexPath.h" + +@implementation UIView (SASimilarPath) + +- (NSString *)sensorsdata_similarPath { + // 是否支持限定元素位置功能 + BOOL enableSupportSimilarPath = [NSStringFromClass(self.class) isEqualToString:@"UITabBarButton"]; + if (enableSupportSimilarPath && self.sensorsdata_elementPosition) { + return [NSString stringWithFormat:@"%@[-]",NSStringFromClass(self.class)]; + } else { + return self.sensorsdata_itemPath; + } +} + +@end + +@implementation UISegmentedControl (SASimilarPath) + +- (NSString *)sensorsdata_similarPath { + return [NSString stringWithFormat:@"%@/UISegment[-]", super.sensorsdata_itemPath]; +} + +@end + +@implementation UITableViewCell (SASimilarPath) + +- (NSString *)sensorsdata_similarPath { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%@[%ld][-]", NSStringFromClass(self.class), (long)indexPath.section]; + } + return self.sensorsdata_itemPath; +} + +@end + +@implementation UICollectionViewCell (SASimilarPath) + +- (NSString *)sensorsdata_similarPath { + NSIndexPath *indexPath = self.sensorsdata_IndexPath; + if (indexPath) { + return [NSString stringWithFormat:@"%@[%ld][-]", NSStringFromClass(self.class), (long)indexPath.section]; + } else { + return super.sensorsdata_similarPath; + } +} + +@end diff --git a/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.h b/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.h new file mode 100644 index 00000000..27b57871 --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.h @@ -0,0 +1,31 @@ +// +// UIViewController+SAInternalProperties.h +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import +#import "SAUIInternalProperties.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIViewController (SAInternalProperties) + +@end + +NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.m b/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.m new file mode 100644 index 00000000..8868592e --- /dev/null +++ b/SensorsAnalyticsSDK/UIRelated/UIViewController+SAInternalProperties.m @@ -0,0 +1,52 @@ +// +// UIViewController+SAInternalProperties.m +// SensorsAnalyticsSDK +// +// Created by 陈玉国 on 2022/8/30. +// Copyright © 2015-2022 Sensors Data Co., Ltd. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag on this file. +#endif + +#import "UIViewController+SAInternalProperties.h" +#import "SACommonUtility.h" +#import "UIView+SAElementContent.h" + +@implementation UIViewController (SAInternalProperties) + +- (NSString *)sensorsdata_screenName { + return NSStringFromClass(self.class); +} + +- (NSString *)sensorsdata_title { + __block NSString *titleViewContent = nil; + __block NSString *controllerTitle = nil; + [SACommonUtility performBlockOnMainThread:^{ + titleViewContent = self.navigationItem.titleView.sensorsdata_elementContent; + controllerTitle = self.navigationItem.title; + }]; + if (titleViewContent.length > 0) { + return titleViewContent; + } + + if (controllerTitle.length > 0) { + return controllerTitle; + } + return nil; +} + +@end diff --git a/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfig.m b/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfig.m index 95fad237..1c73d032 100644 --- a/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfig.m +++ b/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfig.m @@ -28,6 +28,10 @@ #import "UIView+SAAutoTrack.h" #import "SAValidator.h" #import "SAViewNode.h" +#import "UIView+SAVisualizedViewPath.h" +#import "UIView+SAElementContent.h" +#import "UIView+SAElementPosition.h" + static id dictionaryValueForKey(NSDictionary *dic, NSString *key) { if (![SAValidator isValidDictionary:dic]) { diff --git a/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfigSources.m b/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfigSources.m index 205046f4..c144079d 100644 --- a/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfigSources.m +++ b/SensorsAnalyticsSDK/Visualized/Config/SAVisualPropertiesConfigSources.m @@ -25,7 +25,6 @@ #import "SAVisualPropertiesConfigSources.h" #import "UIViewController+SAAutoTrack.h" #import "SAConstants+Private.h" -#import "SAAutoTrackUtils.h" #import "SAReadWriteLock.h" #import "SAReachability.h" #import "SAStoreManager.h" diff --git a/SensorsAnalyticsSDK/Visualized/ElementPath/SAVisualizedViewPathProperty.h b/SensorsAnalyticsSDK/Visualized/ElementPath/SAVisualizedViewPathProperty.h index c9b228ca..406c2144 100644 --- a/SensorsAnalyticsSDK/Visualized/ElementPath/SAVisualizedViewPathProperty.h +++ b/SensorsAnalyticsSDK/Visualized/ElementPath/SAVisualizedViewPathProperty.h @@ -21,17 +21,6 @@ #import -#pragma mark - ViewPath -@protocol SAAutoTrackViewPathProperty - -@optional -/// $AppClick 某个元素的相对路径,拼接 $element_path,单个元素包含序号 -@property (nonatomic, copy, readonly) NSString *sensorsdata_itemPath; - -/// 元素相似路径,可能包含 [-] -@property (nonatomic, copy, readonly) NSString *sensorsdata_similarPath; -@end - #pragma mark - Visualized // 可视化全埋点&点击分析 上传页面信息相关协议 @@ -47,9 +36,6 @@ /// 元素子视图 @property (nonatomic, copy, readonly) NSArray *sensorsdata_subElements; -/// 当前元素的路径 -@property (nonatomic, copy, readonly) NSString *sensorsdata_elementPath; - /// App 内嵌 H5 元素的元素选择器 @property (nonatomic, copy, readonly) NSString *sensorsdata_elementSelector; diff --git a/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.h b/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.h similarity index 57% rename from SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.h rename to SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.h index eae76df5..d9754274 100644 --- a/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.h +++ b/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - UIView -@interface UIView (SAElementPath) +@interface UIView (SAVisualizedViewPath) /// 判断 ReactNative 元素是否可点击 - (BOOL)sensorsdata_clickableForRNView; @@ -38,49 +38,43 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface UIScrollView (SAElementPath) +@interface UIScrollView (SAVisualizedViewPath) @end -@interface WKWebView (SAElementPath) +@interface WKWebView (SAVisualizedViewPath) @end -@interface UIWindow (SAElementPath) +@interface UIWindow (SAVisualizedViewPath) @end @interface SAWebElementView (SAElementPath) @end #pragma mark - UIControl -@interface UISwitch (SAElementPath) +@interface UISwitch (SAVisualizedViewPath) @end -@interface UIStepper (SAElementPath) +@interface UIStepper (SAVisualizedViewPath) @end -@interface UISegmentedControl(SAElementPath) +@interface UISlider (SAVisualizedViewPath) @end -@interface UISlider (SAElementPath) -@end - -@interface UIPageControl (SAElementPath) +@interface UIPageControl (SAVisualizedViewPath) @end #pragma mark - TableView & Cell -@interface UITableView (SAElementPath) -@end - -@interface UITableViewHeaderFooterView (SAElementPath) +@interface UITableView (SAVisualizedViewPath) @end -@interface UICollectionView (SAElementPath) +@interface UICollectionView (SAVisualizedViewPath) @end -@interface UITableViewCell (SAElementPath) +@interface UITableViewCell (SAVisualizedViewPath) @end -@interface UICollectionViewCell (SAElementPath) +@interface UICollectionViewCell (SAVisualizedViewPath) @end NS_ASSUME_NONNULL_END diff --git a/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.m b/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.m similarity index 68% rename from SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.m rename to SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.m index c055c7f4..c3fb5f4c 100644 --- a/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAElementPath.m +++ b/SensorsAnalyticsSDK/Visualized/ElementPath/UIView+SAVisualizedViewPath.m @@ -23,15 +23,20 @@ #endif #import -#import "UIView+SAElementPath.h" +#import "UIView+SAVisualizedViewPath.h" #import "UIView+SAAutoTrack.h" #import "UIViewController+SAAutoTrack.h" #import "UIViewController+SAElementPath.h" #import "SAVisualizedUtils.h" -#import "SAAutoTrackUtils.h" #import "SAConstants+Private.h" #import "SensorsAnalyticsSDK+Private.h" #import "SAViewElementInfoFactory.h" +#import "UIView+SAItemPath.h" +#import "UIView+SASimilarPath.h" +#import "UIView+SAElementPosition.h" +#import "UIView+SAInternalProperties.h" +#import "UIView+SARNView.h" +#import "SAUIProperties.h" typedef BOOL (*SAClickableImplementation)(id, SEL, UIView *); @@ -41,7 +46,7 @@ static void *const kSAIsDisableRNSubviewsInteractivePropertyName = (void *)&kSAIsDisableRNSubviewsInteractivePropertyName; #pragma mark - UIView -@implementation UIView (SAElementPath) +@implementation UIView (SAVisualizedViewPath) - (int)jjf_fingerprintVersion { @@ -79,7 +84,7 @@ - (BOOL)sensorsdata_isVisible { } // RN 项目,view 覆盖层次比较多,被覆盖元素,可以直接屏蔽,防止被覆盖元素可圈选 - BOOL isRNView = [SAAutoTrackUtils isKindOfRNView:self]; + BOOL isRNView = [self isSensorsdataRNView]; if (isRNView && [SAVisualizedUtils isCoveredForView:self]) { return NO; } @@ -204,37 +209,6 @@ - (BOOL)sensorsdata_isAutoTrackAppClick { return elementInfo.isVisualView; } -#pragma mark SAAutoTrackViewPathProperty -- (NSString *)sensorsdata_itemPath { - /* 忽略路径 - UITableViewWrapperView 为 iOS11 以下 UITableView 与 cell 之间的 view - _UITextFieldCanvasView 和 _UISearchBarFieldEditor 都是 UISearchBar 内部私有 view - 在输入状态下 ...UISearchBarTextField/_UISearchBarFieldEditor/_UITextFieldCanvasView/... - 非输入状态下 .../UISearchBarTextField/_UITextFieldCanvasView - 并且 _UITextFieldCanvasView 是个私有 view,无法获取元素内容(目前通过 nextResponder 获取 textField 采集内容)。方便路径统一,所以忽略 _UISearchBarFieldEditor 路径 - */ - if ([SAVisualizedUtils isIgnoredItemPathWithView:self]) { - return nil; - } - - NSString *className = NSStringFromClass(self.class); - NSInteger index = [SAAutoTrackUtils itemIndexForResponder:self]; - if (index < 0) { // -1 - return className; - } - return [NSString stringWithFormat:@"%@[%ld]", className, (long)index]; -} - -- (NSString *)sensorsdata_similarPath { - // 是否支持限定元素位置功能 - BOOL enableSupportSimilarPath = [NSStringFromClass(self.class) isEqualToString:@"UITabBarButton"]; - if (enableSupportSimilarPath && self.sensorsdata_elementPosition) { - return [NSString stringWithFormat:@"%@[-]",NSStringFromClass(self.class)]; - } else { - return self.sensorsdata_itemPath; - } -} - #pragma mark SAVisualizedViewPathProperty // 当前元素,前端是否渲染成可交互 - (BOOL)sensorsdata_enableAppClick { @@ -248,7 +222,7 @@ - (NSString *)sensorsdata_elementValidContent { 针对 RN 元素,上传页面信息中的元素内容,和 RN 插件触发全埋点一致,不遍历子视图元素内容 获取 RN 元素自定义属性,会尝试遍历子视图 */ - if ([SAAutoTrackUtils isKindOfRNView:self]) { + if ([self isSensorsdataRNView]) { return [self.accessibilityLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } return self.sensorsdata_elementContent; @@ -286,19 +260,6 @@ - (NSArray *)sensorsdata_subElements { return newSubViews; } -- (NSString *)sensorsdata_elementPath { - // 处理特殊控件 - // UISegmentedControl 嵌套 UISegment 作为选项单元格,特殊处理 - if ([NSStringFromClass(self.class) isEqualToString:@"UISegment"]) { - UISegmentedControl *segmentedControl = (UISegmentedControl *)[self superview]; - if ([segmentedControl isKindOfClass:UISegmentedControl.class]) { - return [SAVisualizedUtils viewSimilarPathForView:segmentedControl atViewController:segmentedControl.sensorsdata_viewController]; - } - } - // 支持自定义属性,可见元素均上传 elementPath - return [SAVisualizedUtils viewSimilarPathForView:self atViewController:self.sensorsdata_viewController]; -} - - (BOOL)sensorsdata_isFromWeb { return NO; } @@ -313,7 +274,7 @@ - (BOOL)sensorsdata_isListView { - (NSString *)sensorsdata_screenName { // 解析 ReactNative 元素页面名称 - if ([SAAutoTrackUtils isKindOfRNView:self]) { + if ([self isSensorsdataRNView]) { NSDictionary *screenProperties = [self sensorsdata_RNElementScreenProperties]; // 如果 ReactNative 页面信息为空,则使用 Native 的 NSString *screenName = screenProperties[kSAEventPropertyScreenName]; @@ -324,7 +285,7 @@ - (NSString *)sensorsdata_screenName { // 解析 Native 元素页面信息 if (self.sensorsdata_viewController) { - NSDictionary *autoTrackScreenProperties = [SAAutoTrackUtils propertiesWithViewController:self.sensorsdata_viewController]; + NSDictionary *autoTrackScreenProperties = [SAUIProperties propertiesWithViewController:self.sensorsdata_viewController]; return autoTrackScreenProperties[kSAEventPropertyScreenName]; } return nil; @@ -332,7 +293,7 @@ - (NSString *)sensorsdata_screenName { - (NSString *)sensorsdata_title { // 处理 ReactNative 元素 - if ([SAAutoTrackUtils isKindOfRNView:self]) { + if ([self isSensorsdataRNView]) { NSDictionary *screenProperties = [self sensorsdata_RNElementScreenProperties]; // 如果 ReactNative 的 screenName 不存在,则判断页面信息不存在,即使用 Native 逻辑 if (screenProperties[kSAEventPropertyScreenName]) { @@ -342,7 +303,7 @@ - (NSString *)sensorsdata_title { // 处理 Native 元素 if (self.sensorsdata_viewController) { - NSDictionary *autoTrackScreenProperties = [SAAutoTrackUtils propertiesWithViewController:self.sensorsdata_viewController]; + NSDictionary *autoTrackScreenProperties = [SAUIProperties propertiesWithViewController:self.sensorsdata_viewController]; return autoTrackScreenProperties[kSAEventPropertyTitle]; } return nil; @@ -379,7 +340,7 @@ - (void)setSensorsdata_isDisableRNSubviewsInteractive:(BOOL)sensorsdata_isDisabl @end -@implementation UIScrollView (SAElementPath) +@implementation UIScrollView (SAVisualizedViewPath) - (CGRect)sensorsdata_visibleFrame { CGRect showRect = [self convertRect:self.bounds toView:nil]; @@ -396,7 +357,7 @@ - (CGRect)sensorsdata_visibleFrame { @end -@implementation WKWebView (SAElementPath) +@implementation WKWebView (SAVisualizedViewPath) - (NSArray *)sensorsdata_subElements { NSArray *subElements = [SAVisualizedUtils analysisWebElementWithWebView:self]; @@ -409,7 +370,7 @@ - (NSArray *)sensorsdata_subElements { @end -@implementation UIWindow (SAElementPath) +@implementation UIWindow (SAVisualizedViewPath) - (NSArray *)sensorsdata_subElements { if (!self.rootViewController) { @@ -445,7 +406,7 @@ - (NSArray *)sensorsdata_subElements { @end -@implementation SAWebElementView (SAElementPath) +@implementation SAWebElementView (SAVisualizedViewPath) #pragma mark SAVisualizedViewPathProperty - (NSString *)sensorsdata_title { @@ -494,7 +455,7 @@ - (NSString *)sensorsdata_elementPosition { @end #pragma mark - UIControl -@implementation UISwitch (SAElementPath) +@implementation UISwitch (SAVisualizedViewPath) - (NSString *)sensorsdata_elementValidContent { return nil; @@ -502,7 +463,7 @@ - (NSString *)sensorsdata_elementValidContent { @end -@implementation UIStepper (SAElementPath) +@implementation UIStepper (SAVisualizedViewPath) - (NSString *)sensorsdata_elementValidContent { return nil; @@ -510,21 +471,7 @@ - (NSString *)sensorsdata_elementValidContent { @end -@implementation UISegmentedControl (SAElementPath) - -- (NSString *)sensorsdata_itemPath { - // 支持单个 UISegment 创建事件。UISegment 是 UIImageView 的私有子类,表示UISegmentedControl 单个选项的显示区域 - NSString *subPath = [NSString stringWithFormat:@"UISegment[%ld]", (long)self.selectedSegmentIndex]; - return [NSString stringWithFormat:@"%@/%@", super.sensorsdata_itemPath, subPath]; -} - -- (NSString *)sensorsdata_similarPath { - return [NSString stringWithFormat:@"%@/UISegment[-]", super.sensorsdata_itemPath]; -} - -@end - -@implementation UISlider (SAElementPath) +@implementation UISlider (SAVisualizedViewPath) - (NSString *)sensorsdata_elementValidContent { return nil; @@ -532,7 +479,7 @@ - (NSString *)sensorsdata_elementValidContent { @end -@implementation UIPageControl (SAElementPath) +@implementation UIPageControl (SAVisualizedViewPath) - (NSString *)sensorsdata_elementValidContent { return nil; @@ -542,7 +489,7 @@ - (NSString *)sensorsdata_elementValidContent { #pragma mark - TableView & Cell -@implementation UITableView (SAElementPath) +@implementation UITableView (SAVisualizedViewPath) - (NSArray *)sensorsdata_subElements { NSArray *subviews = self.subviews; @@ -562,32 +509,7 @@ - (NSArray *)sensorsdata_subElements { @end -@implementation UITableViewHeaderFooterView (SAElementPath) - -- (NSString *)sensorsdata_itemPath { - UITableView *tableView = (UITableView *)self.superview; - - while (![tableView isKindOfClass:UITableView.class]) { - tableView = (UITableView *)tableView.superview; - if (!tableView) { - return super.sensorsdata_itemPath; - } - } - for (NSInteger i = 0; i < tableView.numberOfSections; i++) { - if (self == [tableView headerViewForSection:i]) { - return [NSString stringWithFormat:@"[SectionHeader][%ld]", (long)i]; - } - if (self == [tableView footerViewForSection:i]) { - return [NSString stringWithFormat:@"[SectionFooter][%ld]", (long)i]; - } - } - return super.sensorsdata_itemPath; -} - -@end - - -@implementation UICollectionView (SAElementPath) +@implementation UICollectionView (SAVisualizedViewPath) - (NSArray *)sensorsdata_subElements { NSArray *subviews = self.subviews; @@ -607,53 +529,7 @@ - (NSArray *)sensorsdata_subElements { @end -@implementation UITableViewCell (SAElementPath) - -- (NSIndexPath *)sensorsdata_IndexPath { - UITableView *tableView = (UITableView *)[self superview]; - do { - if ([tableView isKindOfClass:UITableView.class]) { - NSIndexPath *indexPath = [tableView indexPathForCell:self]; - return indexPath; - } - } while ((tableView = (UITableView *)[tableView superview])); - return nil; -} - -#pragma mark SAAutoTrackViewPathProperty - -- (NSString *)sensorsdata_itemPath { - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [self sensorsdata_itemPathWithIndexPath:indexPath]; - } - return [super sensorsdata_itemPath]; -} - -- (NSString *)sensorsdata_similarPath { - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [self sensorsdata_similarPathWithIndexPath:indexPath]; - } - return self.sensorsdata_itemPath; -} - -- (NSString *)sensorsdata_itemPathWithIndexPath:(NSIndexPath *)indexPath { - return [NSString stringWithFormat:@"%@[%ld][%ld]", NSStringFromClass(self.class), (long)indexPath.section, (long)indexPath.row]; -} - -- (NSString *)sensorsdata_similarPathWithIndexPath:(NSIndexPath *)indexPath { - return [NSString stringWithFormat:@"%@[%ld][-]", NSStringFromClass(self.class), (long)indexPath.section]; -} - -#pragma mark SAAutoTrackViewProperty -- (NSString *)sensorsdata_elementPosition { - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [NSString stringWithFormat:@"%ld:%ld", (long)indexPath.section, (long)indexPath.row]; - } - return nil; -} +@implementation UITableViewCell (SAVisualizedViewPath) - (BOOL)sensorsdata_isListView { return self.sensorsdata_elementPosition != nil; @@ -661,60 +537,7 @@ - (BOOL)sensorsdata_isListView { @end -@implementation UICollectionViewCell (SAElementPath) - -- (NSIndexPath *)sensorsdata_IndexPath { - UICollectionView *collectionView = (UICollectionView *)[self superview]; - if ([collectionView isKindOfClass:UICollectionView.class]) { - NSIndexPath *indexPath = [collectionView indexPathForCell:self]; - return indexPath; - } - return nil; -} - -#pragma mark SAAutoTrackViewPathProperty -- (NSString *)sensorsdata_itemPath { - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [self sensorsdata_itemPathWithIndexPath:indexPath]; - } - return [super sensorsdata_itemPath]; -} - -- (NSString *)sensorsdata_similarPath { - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [self sensorsdata_similarPathWithIndexPath:indexPath]; - } else { - return super.sensorsdata_similarPath; - } -} - -- (NSString *)sensorsdata_itemPathWithIndexPath:(NSIndexPath *)indexPath { - return [NSString stringWithFormat:@"%@[%ld][%ld]", NSStringFromClass(self.class), (long)indexPath.section, (long)indexPath.item]; -} - -- (NSString *)sensorsdata_similarPathWithIndexPath:(NSIndexPath *)indexPath { - SAViewElementInfo *elementInfo = [SAViewElementInfoFactory elementInfoWithView:self]; - if (!elementInfo.isSupportElementPosition) { - return [self sensorsdata_itemPathWithIndexPath:indexPath]; - } - return [NSString stringWithFormat:@"%@[%ld][-]", NSStringFromClass(self.class), (long)indexPath.section]; -} - -#pragma mark SAAutoTrackViewProperty -- (NSString *)sensorsdata_elementPosition { - SAViewElementInfo *elementInfo = [SAViewElementInfoFactory elementInfoWithView:self]; - if (!elementInfo.isSupportElementPosition) { - return nil; - } - - NSIndexPath *indexPath = self.sensorsdata_IndexPath; - if (indexPath) { - return [NSString stringWithFormat:@"%ld:%ld", (long)indexPath.section, (long)indexPath.item]; - } - return nil; -} +@implementation UICollectionViewCell (SAVisualizedViewPath) - (BOOL)sensorsdata_isListView { return self.sensorsdata_elementPosition != nil; diff --git a/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.h b/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.h index 879432ce..71731359 100644 --- a/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.h +++ b/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.h @@ -25,16 +25,12 @@ NS_ASSUME_NONNULL_BEGIN -@interface UIViewController (SAElementPath) +@interface UIViewController (SAElementPath) - (void)sensorsdata_visualize_viewDidAppear:(BOOL)animated; @end -@interface UIAlertController(SAElementPath) - -@end - @interface UITabBarController (SAElementPath) @end diff --git a/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.m b/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.m index c6bbc573..07a20dd7 100644 --- a/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.m +++ b/SensorsAnalyticsSDK/Visualized/ElementPath/UIViewController+SAElementPath.m @@ -24,13 +24,13 @@ #import "UIViewController+SAElementPath.h" #import "SAVisualizedUtils.h" -#import "SAAutoTrackUtils.h" -#import "UIView+SAElementPath.h" +#import "UIView+SAVisualizedViewPath.h" #import "SensorsAnalyticsSDK+Private.h" #import "SAConstants+Private.h" #import "SAVisualizedObjectSerializerManager.h" #import "SAVisualizedManager.h" #import "SAAutoTrackManager.h" +#import "SAUIProperties.h" @implementation UIViewController (SAElementPath) @@ -109,19 +109,6 @@ - (void)sensorsdata_readyEnterViewController { @end -@implementation UIAlertController (SAElementPath) - -- (NSString *)sensorsdata_similarPath { - NSString *className = NSStringFromClass(self.class); - NSInteger index = [SAAutoTrackUtils itemIndexForResponder:self]; - if (index < 0) { // -1 - return className; - } - return [NSString stringWithFormat:@"%@[%ld]", className, (long)index]; -} - -@end - @implementation UITabBarController (SAElementPath) - (NSArray *)sensorsdata_subElements { NSMutableArray *subElements = [NSMutableArray array]; diff --git a/SensorsAnalyticsSDK/Visualized/EventCheck/SAEventIdentifier.m b/SensorsAnalyticsSDK/Visualized/EventCheck/SAEventIdentifier.m index a1afed17..5f22694d 100644 --- a/SensorsAnalyticsSDK/Visualized/EventCheck/SAEventIdentifier.m +++ b/SensorsAnalyticsSDK/Visualized/EventCheck/SAEventIdentifier.m @@ -25,7 +25,6 @@ #import "SAEventIdentifier.h" #import "UIViewController+SAAutoTrack.h" #import "SAConstants+Private.h" -#import "SAAutoTrackUtils.h" @implementation SAEventIdentifier diff --git a/SensorsAnalyticsSDK/Visualized/SAVisualizedAbstractMessage.m b/SensorsAnalyticsSDK/Visualized/SAVisualizedAbstractMessage.m index f9449a74..d03b07f2 100644 --- a/SensorsAnalyticsSDK/Visualized/SAVisualizedAbstractMessage.m +++ b/SensorsAnalyticsSDK/Visualized/SAVisualizedAbstractMessage.m @@ -28,12 +28,12 @@ #import "SensorsAnalyticsSDK.h" #import "SALog.h" #import "UIViewController+SAAutoTrack.h" -#import "SAAutoTrackUtils.h" #import "SAVisualizedObjectSerializerManager.h" #import "SAConstants+Private.h" #import "SAVisualizedUtils.h" #import "SAJSONUtil.h" #import "SAVisualizedManager.h" +#import "SAUIProperties.h" @interface SAVisualizedAbstractMessage () @@ -102,11 +102,11 @@ - (NSData *)JSONDataWithFeatureCode:(NSString *)featureCode { // 获取当前页面 UIViewController *currentViewController = serializerManager.lastViewScreenController; if (!currentViewController) { - currentViewController = [SAAutoTrackUtils currentViewController]; + currentViewController = [SAUIProperties currentViewController]; } // 解析页面信息 - NSDictionary *autoTrackScreenProperties = [SAAutoTrackUtils propertiesWithViewController:currentViewController]; + NSDictionary *autoTrackScreenProperties = [SAUIProperties propertiesWithViewController:currentViewController]; screenName = autoTrackScreenProperties[kSAEventPropertyScreenName]; pageName = autoTrackScreenProperties[kSAEventPropertyScreenName]; title = autoTrackScreenProperties[kSAEventPropertyTitle]; diff --git a/SensorsAnalyticsSDK/Visualized/SAVisualizedManager.m b/SensorsAnalyticsSDK/Visualized/SAVisualizedManager.m index c1767bc4..a5e91dac 100644 --- a/SensorsAnalyticsSDK/Visualized/SAVisualizedManager.m +++ b/SensorsAnalyticsSDK/Visualized/SAVisualizedManager.m @@ -37,6 +37,7 @@ #import "SAJSONUtil.h" #import "SASwizzle.h" #import "SALog.h" +#import "UIView+SAInternalProperties.h" @interface SAVisualizedManager() diff --git a/SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.m b/SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.m index fb0d92a4..4ee33651 100644 --- a/SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.m +++ b/SensorsAnalyticsSDK/Visualized/SAVisualizedUtils.m @@ -24,16 +24,19 @@ #import "SAVisualizedUtils.h" #import "SAWebElementView.h" -#import "UIView+SAElementPath.h" +#import "UIView+SAVisualizedViewPath.h" #import "SAVisualizedViewPathProperty.h" #import "SAVisualizedObjectSerializerManager.h" #import "SAConstants+Private.h" #import "SAVisualizedManager.h" -#import "SAAutoTrackUtils.h" #import "UIView+SAAutoTrack.h" #import "SACommonUtility.h" #import "SAJavaScriptBridgeManager.h" #import "SALog.h" +#import "UIView+SAItemPath.h" +#import "UIView+SASimilarPath.h" +#import "UIAlertController+SASimilarPath.h" +#import "SAUIProperties.h" /// 遍历查找页面最大层数,用于判断元素是否被覆盖 static NSInteger kSAVisualizedFindMaxPageLevel = 4; @@ -498,34 +501,7 @@ + (NSString *)viewSimilarPathForView:(UIView *)view atViewController:(UIViewCont if ([self isIgnoredViewPathForViewController:viewController]) { return nil; } - - NSMutableArray *viewPathArray = [NSMutableArray array]; - BOOL isContainSimilarPath = NO; - - do { - if (isContainSimilarPath) { // 防止 cell 等列表嵌套,被拼上多个 [-] - if (view.sensorsdata_itemPath) { - [viewPathArray addObject:view.sensorsdata_itemPath]; - } - } else { - NSString *currentSimilarPath = view.sensorsdata_similarPath; - if (currentSimilarPath) { - [viewPathArray addObject:currentSimilarPath]; - if ([currentSimilarPath containsString:@"[-]"]) { - isContainSimilarPath = YES; - } - } - } - } while ((view = (id)view.nextResponder) && [view isKindOfClass:UIView.class]); - - if ([view isKindOfClass:UIAlertController.class]) { - UIViewController *viewController = (UIViewController *)view; - [viewPathArray addObject:viewController.sensorsdata_similarPath]; - } - - NSString *viewPath = [[[viewPathArray reverseObjectEnumerator] allObjects] componentsJoinedByString:@"/"]; - - return viewPath; + return [SAUIProperties elementPathForView:view atViewController:viewController]; } /// 当前 view 所在同类页面序号 diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/DebugLog/SAVisualizedDebugLogTracker.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/DebugLog/SAVisualizedDebugLogTracker.m index 96a2f366..b0566067 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/DebugLog/SAVisualizedDebugLogTracker.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/DebugLog/SAVisualizedDebugLogTracker.m @@ -25,7 +25,6 @@ #import "SAVisualizedDebugLogTracker.h" #import "SAVisualizedLogger.h" #import "SAVisualizedUtils.h" -#import "SAAutoTrackUtils.h" #import "SAViewNode.h" #import "SALog+Private.h" #import "UIView+SAVisualProperties.h" diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/SAVisualPropertiesTracker.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/SAVisualPropertiesTracker.m index 4c3f8c7c..304ed80c 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/SAVisualPropertiesTracker.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/SAVisualPropertiesTracker.m @@ -32,11 +32,11 @@ #import "SAVisualizedLogger.h" #import "SAJavaScriptBridgeManager.h" #import "SAAlertController.h" -#import "SAAutoTrackUtils.h" #import "UIView+SAVisualProperties.h" #import "SAJSONUtil.h" #import "SALog.h" #import "SAConstants+Private.h" +#import "UIView+SAElementPosition.h" @interface SAVisualPropertiesTracker() diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNode.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNode.m index 0b09662f..8e9853bb 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNode.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNode.m @@ -23,10 +23,9 @@ #endif #import "SAViewNode.h" -#import "SAAutoTrackUtils.h" #import "UIView+SAVisualProperties.h" #import "SACommonUtility.h" -#import "UIView+SAElementPath.h" +#import "UIView+SAVisualizedViewPath.h" #import "UIView+SAAutoTrack.h" #import "SAConstants+Private.h" #import "SAVisualizedUtils.h" diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeFactory.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeFactory.m index e6a3fb6d..c24c6fb6 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeFactory.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeFactory.m @@ -24,8 +24,8 @@ #import "SAViewNodeFactory.h" #import "SAVisualizedUtils.h" -#import "SAAutoTrackUtils.h" #import "SAViewNode.h" +#import "UIView+SARNView.h" @implementation SAViewNodeFactory @@ -41,7 +41,7 @@ + (nullable SAViewNode *)viewNodeWithView:(UIView *)view { } else if ([NSStringFromClass(view.class) isEqualToString:@"UITabBarButton"]) { // UITabBarItem 点击事件,支持限定元素位置 return [[SATabBarButtonNode alloc] initWithView:view]; - } else if ([SAAutoTrackUtils isKindOfRNView:view]) { + } else if ([view isSensorsdataRNView]) { return [[SARNViewNode alloc] initWithView:view]; } else if ([view isKindOfClass:WKWebView.class]) { return [[SAWKWebViewNode alloc] initWithView:view]; diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeTree.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeTree.m index ac51c6bb..c8c439ec 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeTree.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/SAViewNodeTree.m @@ -24,7 +24,7 @@ #import "SAViewNodeTree.h" #import "UIView+SAVisualProperties.h" -#import "UIView+SAElementPath.h" +#import "UIView+SAVisualizedViewPath.h" #import "SAConstants+Private.h" #import "SAVisualizedUtils.h" #import "SAViewNodeFactory.h" diff --git a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/UIView+SAVisualProperties.m b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/UIView+SAVisualProperties.m index 7e13e1aa..9f65970e 100644 --- a/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/UIView+SAVisualProperties.m +++ b/SensorsAnalyticsSDK/Visualized/VisualProperties/ViewNode/UIView+SAVisualProperties.m @@ -25,7 +25,8 @@ #import "UIView+SAVisualProperties.h" #import "SAVisualizedManager.h" #import -#import "SAAutoTrackUtils.h" +#import "UIView+SARNView.h" +#import "UIView+SensorsAnalytics.h" static void *const kSAViewNodePropertyName = (void *)&kSAViewNodePropertyName; @@ -191,7 +192,7 @@ - (NSString *)sensorsdata_propertyContent { return nil; #pragma clang diagnostic pop } - if ([SAAutoTrackUtils isKindOfRNView:self]) { // RN 元素,https://reactnative.dev + if ([self isSensorsdataRNView]) { // RN 元素,https://reactnative.dev NSString *content = [self.accessibilityLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if (content.length > 0) { return content;