diff --git a/.swiftlint.yml b/.swiftlint.yml index fae5d675c..fd595fa46 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,6 +30,8 @@ file_length: identifier_name: excluded: - id + - c + - r - ^ph_.*$ function_body_length: diff --git a/CHANGELOG.md b/CHANGELOG.md index b5db622f0..eaeb626b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- feat: ability to add a custom label to autocapture elements ([#271](https://github.com/PostHog/posthog-ios/pull/271)) + ## 3.16.1 - 2024-12-04 - fix: screen flicker when capturing a screenshot when a sensitive text field is on screen ([#270](https://github.com/PostHog/posthog-ios/pull/270)) diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index c61a367ed..84faa24e0 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -121,6 +121,7 @@ 69F518382BB2BA0100F52C14 /* PostHogSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F518372BB2BA0100F52C14 /* PostHogSwizzler.swift */; }; 69F5183A2BB2BA8300F52C14 /* UIApplicationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F518392BB2BA8300F52C14 /* UIApplicationTracker.swift */; }; DA26419C2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA26419A2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift */; }; + DA4932FD2D0102950092C213 /* AssociatedKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4932FC2D0102910092C213 /* AssociatedKeys.swift */; }; DA5AA7192CE245D2004EFB99 /* UIApplication+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5AA7132CE245CD004EFB99 /* UIApplication+.swift */; }; DA5B85882CD21CBB00686389 /* AutocaptureEventProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */; }; DA979D7B2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA979D7A2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift */; }; @@ -128,6 +129,8 @@ DAC699EC2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC699EB2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift */; }; DACF6D5D2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACF6D5C2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift */; }; DAD5DD0C2CB6DEF30087387B /* PostHogMaskViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD5DD072CB6DEE70087387B /* PostHogMaskViewModifier.swift */; }; + DAD76A212D006AEE003E1A43 /* UIView+PostHogLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD76A1B2D006AE8003E1A43 /* UIView+PostHogLabel.swift */; }; + DAD76A242D006C15003E1A43 /* View+PostHogLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD76A232D006C0B003E1A43 /* View+PostHogLabel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -393,6 +396,7 @@ 69F518372BB2BA0100F52C14 /* PostHogSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSwizzler.swift; sourceTree = ""; }; 69F518392BB2BA8300F52C14 /* UIApplicationTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationTracker.swift; sourceTree = ""; }; DA26419A2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureEventTracker.swift; sourceTree = ""; }; + DA4932FC2D0102910092C213 /* AssociatedKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociatedKeys.swift; sourceTree = ""; }; DA5AA7132CE245CD004EFB99 /* UIApplication+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+.swift"; sourceTree = ""; }; DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocaptureEventProcessing.swift; sourceTree = ""; }; DA8D37242CBEAC02005EBD27 /* PostHogExampleAutocapture.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleAutocapture.xcodeproj; path = PostHogExampleAutocapture/PostHogExampleAutocapture.xcodeproj; sourceTree = ""; }; @@ -401,6 +405,8 @@ DAC699EB2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingPickerViewDelegate.swift; sourceTree = ""; }; DACF6D5C2CD2F5BC00F14133 /* PostHogAutocaptureIntegrationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureIntegrationSpec.swift; sourceTree = ""; }; DAD5DD072CB6DEE70087387B /* PostHogMaskViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogMaskViewModifier.swift; sourceTree = ""; }; + DAD76A1B2D006AE8003E1A43 /* UIView+PostHogLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+PostHogLabel.swift"; sourceTree = ""; }; + DAD76A232D006C0B003E1A43 /* View+PostHogLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+PostHogLabel.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -521,6 +527,7 @@ 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */, 699C5FE52C20178E007DB818 /* UUIDUtils.swift */, 690B2DF22C205B5600AE3B45 /* TimeBasedEpochGenerator.swift */, + DA4932FC2D0102910092C213 /* AssociatedKeys.swift */, ); path = Utils; sourceTree = ""; @@ -766,6 +773,8 @@ DA26419B2CC0499300CB427B /* Autocapture */ = { isa = PBXGroup; children = ( + DAD76A222D006BF7003E1A43 /* SwiftUI */, + DAD76A1B2D006AE8003E1A43 /* UIView+PostHogLabel.swift */, DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */, DAC699EB2CCA73E5000D1D6B /* ForwardingPickerViewDelegate.swift */, DAC699D52CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift */, @@ -782,6 +791,14 @@ name = Products; sourceTree = ""; }; + DAD76A222D006BF7003E1A43 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + DAD76A232D006C0B003E1A43 /* View+PostHogLabel.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1149,6 +1166,7 @@ 69261D252AD9787A00232EC7 /* PostHogExtensions.swift in Sources */, 3AE3FB4E2993D1D600AFFC18 /* PostHogStorageManager.swift in Sources */, 3AE3FB49299391DF00AFFC18 /* PostHogStorage.swift in Sources */, + DAD76A212D006AEE003E1A43 /* UIView+PostHogLabel.swift in Sources */, 69261D232AD9784200232EC7 /* PostHogVersion.swift in Sources */, 69779BEC2AE68E6900D7A48E /* UIViewController.swift in Sources */, 3A0F108929C9BD76002C0084 /* Errors.swift in Sources */, @@ -1164,6 +1182,7 @@ 69ED1A5C2C7F15F300FE7A91 /* PostHogSessionManager.swift in Sources */, 69F23A742BB3088E001194F6 /* URLSessionSwizzler.swift in Sources */, 69F518142BAC7F4300F52C14 /* Date+Util.swift in Sources */, + DA4932FD2D0102950092C213 /* AssociatedKeys.swift in Sources */, 69F5183A2BB2BA8300F52C14 /* UIApplicationTracker.swift in Sources */, 69F518182BAC80A300F52C14 /* String+Util.swift in Sources */, 69ED1A882C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift in Sources */, @@ -1181,6 +1200,7 @@ 690FF0B52AEBBD3C00A0B06B /* DictUtils.swift in Sources */, 69F23A7A2BB309F3001194F6 /* MethodSwizzler.swift in Sources */, 69261D1B2AD9678C00232EC7 /* PostHogEvent.swift in Sources */, + DAD76A242D006C15003E1A43 /* View+PostHogLabel.swift in Sources */, 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */, DA5AA7192CE245D2004EFB99 /* UIApplication+.swift in Sources */, 69EE82CE2BAAC76000EB9542 /* ViewTreeSnapshotStatus.swift in Sources */, diff --git a/PostHog/Autocapture/ForwardingPickerViewDelegate.swift b/PostHog/Autocapture/ForwardingPickerViewDelegate.swift index 1511b7f8a..8feca3ae0 100644 --- a/PostHog/Autocapture/ForwardingPickerViewDelegate.swift +++ b/PostHog/Autocapture/ForwardingPickerViewDelegate.swift @@ -1,5 +1,3 @@ -// swiftlint:disable cyclomatic_complexity - // // ForwardingPickerViewDelegate.swift // PostHog @@ -8,953 +6,66 @@ // #if os(iOS) || targetEnvironment(macCatalyst) - import Foundation import UIKit - enum ForwardingDelegateSelector { - static func selectDelegate(for actualDelegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) -> UIPickerViewDelegate { - // Checking if the actual delegate implements specific methods - let titleForRow = actualDelegate?.responds(to: #selector(UIPickerViewDelegate.pickerView(_:titleForRow:forComponent:))) ?? false - let attributedTitleForRow = actualDelegate?.responds(to: #selector(UIPickerViewDelegate.pickerView(_:attributedTitleForRow:forComponent:))) ?? false - let viewForRow = actualDelegate?.responds(to: #selector(UIPickerViewDelegate.pickerView(_:viewForRow:forComponent:reusing:))) ?? false - let rowHeightForComponent = actualDelegate?.responds(to: #selector(UIPickerViewDelegate.pickerView(_:rowHeightForComponent:))) ?? false - let widthForComponent = actualDelegate?.responds(to: #selector(UIPickerViewDelegate.pickerView(_:widthForComponent:))) ?? false - - // Selecting the appropriate forwarding delegate based on implemented methods - // - // UIPickerViewDelegate includes several `optional` methods that return values. - // - // To ensure that the behavior of the host app is preserved, we must select a forwarding delegate that accurately - // reflects which methods are implemented by the original delegate. This ensures the forwarding delegate - // responds correctly to `responds(to: #selector)` checks and avoids dependeing on abstract default values - - switch (titleForRow, attributedTitleForRow, viewForRow, rowHeightForComponent, widthForComponent) { - case (false, false, false, false, false): - return ForwardingPickerViewDelegate1(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, false, false, false): - return ForwardingPickerViewDelegate2(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, false, false, false): - return ForwardingPickerViewDelegate3(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, true, false, false): - return ForwardingPickerViewDelegate4(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, false, true, false): - return ForwardingPickerViewDelegate5(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, false, false, true): - return ForwardingPickerViewDelegate6(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, false, false, false): - return ForwardingPickerViewDelegate7(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, true, false, false): - return ForwardingPickerViewDelegate8(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, false, true, false): - return ForwardingPickerViewDelegate9(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, false, false, true): - return ForwardingPickerViewDelegate10(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, true, false, false): - return ForwardingPickerViewDelegate11(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, false, true, false): - return ForwardingPickerViewDelegate12(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, false, false, true): - return ForwardingPickerViewDelegate13(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, true, true, false): - return ForwardingPickerViewDelegate14(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, true, false, true): - return ForwardingPickerViewDelegate15(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, false, true, true): - return ForwardingPickerViewDelegate16(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, true, false, false): - return ForwardingPickerViewDelegate17(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, false, true, false): - return ForwardingPickerViewDelegate18(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, false, false, true): - return ForwardingPickerViewDelegate19(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, true, true, false): - return ForwardingPickerViewDelegate20(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, true, false, true): - return ForwardingPickerViewDelegate21(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, false, true, true): - return ForwardingPickerViewDelegate22(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, true, true, false): - return ForwardingPickerViewDelegate23(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, true, false, true): - return ForwardingPickerViewDelegate24(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, false, true, true): - return ForwardingPickerViewDelegate25(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, false, true, true, true): - return ForwardingPickerViewDelegate26(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, true, true, false): - return ForwardingPickerViewDelegate27(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, true, false, true): - return ForwardingPickerViewDelegate28(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, false, true, true): - return ForwardingPickerViewDelegate29(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, false, true, true, true): - return ForwardingPickerViewDelegate30(delegate: actualDelegate, onValueChanged: onValueChanged) - case (false, true, true, true, true): - return ForwardingPickerViewDelegate31(delegate: actualDelegate, onValueChanged: onValueChanged) - case (true, true, true, true, true): - return ForwardingPickerViewDelegate32(delegate: actualDelegate, onValueChanged: onValueChanged) - } - } - } - - private var phForwardingDelegateKey: UInt8 = 0 - extension UIPickerViewDelegate { - var phForwardingDelegate: UIPickerViewDelegate { - get { - objc_getAssociatedObject(self, &phForwardingDelegateKey) as! UIPickerViewDelegate - } - - set { - objc_setAssociatedObject( - self, - &phForwardingDelegateKey, - newValue as UIPickerViewDelegate?, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - } - } - } - - // MARK: - DELEGATE VARIANTS - - /// Combination 1: `didSelectRow` - private class ForwardingPickerViewDelegate1: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - } - - /// Combination 2: `didSelectRow`, `titleForRow` - private class ForwardingPickerViewDelegate2: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - } - - /// Combination 3: `didSelectRow`, `attributedTitleForRow` - private class ForwardingPickerViewDelegate3: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - // Call the value changed callback - valueChangedCallback?() - // Forward the call to the actual delegate - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - } - - /// Combination 4: `didSelectRow`, `viewForRow` - private class ForwardingPickerViewDelegate4: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - } - - /// Combination 5: `didSelectRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate5: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 6: `didSelectRow`, `widthForComponent` - private class ForwardingPickerViewDelegate6: NSObject, UIPickerViewDelegate { + final class ForwardingPickerViewDelegate: NSObject, UIPickerViewDelegate, UIPickerViewDataSource { + // this needs to be week since `actualDelegate` will hold a strong reference to `ForwardingPickerViewDelegate` weak var actualDelegate: UIPickerViewDelegate? private var valueChangedCallback: (() -> Void)? - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero + // We respond to the same selectors that the original delegate responds to + override func responds(to aSelector: Selector!) -> Bool { + actualDelegate?.responds(to: aSelector) ?? false } - } - - /// Combination 7: `didSelectRow`, `titleForRow`, `attributedTitleForRow` - private class ForwardingPickerViewDelegate7: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { actualDelegate = delegate valueChangedCallback = onValueChanged } - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } + // MARK: - UIPickerViewDataSource - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) + func numberOfComponents(in pickerView: UIPickerView) -> Int { + (actualDelegate as? UIPickerViewDataSource)?.numberOfComponents(in: pickerView) ?? 0 } - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + (actualDelegate as? UIPickerViewDataSource)?.pickerView(pickerView, numberOfRowsInComponent: component) ?? 0 } - } - - /// Combination 8: `didSelectRow`, `titleForRow`, `viewForRow` - private class ForwardingPickerViewDelegate8: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } + // MARK: - UIPickerViewDelegate func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { valueChangedCallback?() actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) } - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() } - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero } - } - - /// Combination 9: `didSelectRow`, `titleForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate9: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero } - } - - /// Combination 10: `didSelectRow`, `titleForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate10: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) } - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 11: `didSelectRow`, `attributedTitleForRow`, `viewForRow` - private class ForwardingPickerViewDelegate11: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - } - - /// Combination 12: `didSelectRow`, `attributedTitleForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate12: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 13: `didSelectRow`, `attributedTitleForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate13: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } } - /// Combination 14: `didSelectRow`, `viewForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate14: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 15: `didSelectRow`, `viewForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate15: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 16: `didSelectRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate16: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 17: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `viewForRow` - private class ForwardingPickerViewDelegate17: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - } - - /// Combination 18: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate18: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 19: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate19: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 20: `didSelectRow`, `titleForRow`, `viewForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate20: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 21: `didSelectRow`, `titleForRow`, `viewForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate21: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 22: `didSelectRow`, `titleForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate22: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 23: `didSelectRow`, `attributedTitleForRow`, `viewForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate23: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 24: `didSelectRow`,`attributedTitleForRow`, `viewForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate24: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 25: `didSelectRow`, `attributedTitleForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate25: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 26: `didSelectRow`, `viewForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate26: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 27: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `viewForRow`, `rowHeightForComponent` - private class ForwardingPickerViewDelegate27: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - } - - /// Combination 28: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `viewForRow`, `widthForComponent` - private class ForwardingPickerViewDelegate28: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 29: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate29: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 30: `didSelectRow`, `titleForRow`, `viewForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate30: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 31: `didSelectRow`, `attributedTitleForRow`, `viewForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate31: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero - } - } - - /// Combination 32: `didSelectRow`, `titleForRow`, `attributedTitleForRow`, `viewForRow`, `rowHeightForComponent`, `widthForComponent` - private class ForwardingPickerViewDelegate32: NSObject, UIPickerViewDelegate { - weak var actualDelegate: UIPickerViewDelegate? - private var valueChangedCallback: (() -> Void)? - - init(delegate: UIPickerViewDelegate?, onValueChanged: @escaping () -> Void) { - actualDelegate = delegate - valueChangedCallback = onValueChanged - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - valueChangedCallback?() - actualDelegate?.pickerView?(pickerView, didSelectRow: row, inComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - actualDelegate?.pickerView?(pickerView, titleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - actualDelegate?.pickerView?(pickerView, attributedTitleForRow: row, forComponent: component) - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - actualDelegate?.pickerView?(pickerView, viewForRow: row, forComponent: component, reusing: view) ?? UIView() - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, rowHeightForComponent: component) ?? .zero - } - - func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { - actualDelegate?.pickerView?(pickerView, widthForComponent: component) ?? .zero + extension UIPickerViewDelegate { + var ph_forwardingDelegate: UIPickerViewDelegate? { + get { objc_getAssociatedObject(self, &AssociatedKeys.phForwardingDelegate) as? UIPickerViewDelegate } + set { objc_setAssociatedObject(self, &AssociatedKeys.phForwardingDelegate, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } #endif - -// swiftlint:enable cyclomatic_complexity diff --git a/PostHog/Autocapture/PostHogAutocaptureEventTracker.swift b/PostHog/Autocapture/PostHogAutocaptureEventTracker.swift index be53264ba..b36d6a437 100644 --- a/PostHog/Autocapture/PostHogAutocaptureEventTracker.swift +++ b/PostHog/Autocapture/PostHogAutocaptureEventTracker.swift @@ -13,25 +13,31 @@ let touchCoordinates: CGPoint? let value: String? let screenName: String? - let viewHierarchy: [ViewNode] - let targetClass: String - let accessibilityLabel: String? - let accessibilityIdentifier: String? + let viewHierarchy: [Element] // values >0 means that this event will be debounced for `debounceInterval` let debounceInterval: TimeInterval } - struct ViewNode: CustomStringConvertible { + struct Element { let text: String let targetClass: String - let index: Int - let subviewCount: Int - - // used when building $elements_chain - // Note: For some reason text will not be processed if not present in elements_chain string. - // Couldn't pinpoint to exact place in `posthog` repo where we check for this though - var description: String { - "\(targetClass)\(text.isEmpty ? "" : ":text=\"\(text)\"")" + let baseClasses: String + let label: String? + + var elementsChainEntry: String { + var attributes = [String]() + + if !text.isEmpty { + attributes.append("text=\(text.quoted)") + } + if !baseClasses.isEmpty { + attributes.append("attr__class=\(baseClasses.quoted)") + } + if let label = label, !label.isEmpty { + attributes.append("attr_id=\(label.quoted)") + } + + return attributes.isEmpty ? targetClass : "\(targetClass):\(attributes.joined())" } } @@ -218,23 +224,27 @@ extension UIPickerView { @objc func ph_swizzled_setDelegate(_ delegate: (any UIPickerViewDelegate)?) { - // If the delegate implements pickerView(_:didSelectRow:inComponent:), swizzle it guard let delegate else { + // this just removes the delegate return ph_swizzled_setDelegate(delegate) } + + // if delegate doesn't respond to this selector, then we can't intercept selection changes guard delegate.responds(to: #selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:))) else { return ph_swizzled_setDelegate(delegate) } - let forwardingDelegate = ForwardingDelegateSelector.selectDelegate(for: delegate) { [weak self] in + // wrap in a forwarding delegate so we can intercept calls + let forwardingDelegate = ForwardingPickerViewDelegate(delegate: delegate) { [weak self] in if let data = self?.eventData { PostHogAutocaptureEventTracker.eventProcessor?.process(source: .gestureRecognizer(description: EventType.kValueChange), event: data) } } // Need to keep a strong reference to keep this forwarding delegate instance alive - delegate.phForwardingDelegate = forwardingDelegate + delegate.ph_forwardingDelegate = forwardingDelegate + // call original setter ph_swizzled_setDelegate(forwardingDelegate) } } @@ -250,23 +260,19 @@ .flatMap(UIViewController.ph_topViewController) .flatMap(UIViewController.getViewControllerName), viewHierarchy: sequence(first: self, next: \.superview) - .enumerated() - .map { $1.viewNode(index: $0) }, - targetClass: descriptiveTypeName, - accessibilityLabel: accessibilityLabel, - accessibilityIdentifier: accessibilityIdentifier, + .map(\.toElement), debounceInterval: ph_autocaptureDebounceInterval ) } } - extension UIView { - func viewNode(index: Int) -> PostHogAutocaptureEventTracker.ViewNode { - PostHogAutocaptureEventTracker.ViewNode( + private extension UIView { + var toElement: PostHogAutocaptureEventTracker.Element { + PostHogAutocaptureEventTracker.Element( text: ph_autocaptureText.map(sanitizeText) ?? "", targetClass: descriptiveTypeName, - index: index, - subviewCount: subviews.count + baseClasses: baseTypeNames, + label: postHogLabel ) } } @@ -363,9 +369,35 @@ } } + private func typeName(of type: AnyClass) -> String { + let typeName = String(describing: type) + if let match = typeName.range(of: "^[^<]+", options: .regularExpression) { + // Extracts everything before the first '<' to deal with generics + return String(typeName[match]) + } + return typeName + } + extension NSObject { var descriptiveTypeName: String { - String(describing: type(of: self)) + typeName(of: type(of: self)) + } + + var baseTypeNames: String { + var baseTypes: [String] = [] + var previousType: AnyClass = type(of: self) + + // traverse the inheritance tree + while let superclass = previousType.superclass() { + // NSObject or UIResponder are base types for almost all UIKit and not really helpful here + if superclass == NSObject.self || superclass == UIResponder.self { + return baseTypes.joined(separator: " ") + } + previousType = superclass + baseTypes.append(typeName(of: superclass)) + } + + return baseTypes.joined(separator: " ") } } @@ -381,7 +413,7 @@ @objc var ph_autocaptureText: String? { nil } @objc var ph_autocaptureDebounceInterval: TimeInterval { 0 } @objc func ph_shouldTrack(_: Selector, for _: Any?) -> Bool { - false // by default views are not tracked. Can be overriden in subclasses + false // by default views are not tracked. Can be overridden in subclasses } } @@ -492,7 +524,7 @@ return true } - // TODO: Filter out or obfuscsate strings that look like sensitive data + // TODO: Filter out or obfuscate strings that look like sensitive data // see: https://github.com/PostHog/posthog-js/blob/0cfffcac9bdf1da3fbb9478c1a51170a325bd57f/src/autocapture-utils.ts#L389 private func sanitizeText(_ title: String) -> String { title @@ -527,6 +559,8 @@ static let kLongPress = "long_press" } + // MARK: - Helpers + private extension String { func limit(to length: Int) -> String { if count > length { @@ -535,6 +569,10 @@ } return self } + + var quoted: String { + "\"\(self)\"" + } } #endif diff --git a/PostHog/Autocapture/PostHogAutocaptureIntegration.swift b/PostHog/Autocapture/PostHogAutocaptureIntegration.swift index b8d816217..2af0de0ff 100644 --- a/PostHog/Autocapture/PostHogAutocaptureIntegration.swift +++ b/PostHog/Autocapture/PostHogAutocaptureIntegration.swift @@ -8,6 +8,8 @@ #if os(iOS) || targetEnvironment(macCatalyst) import UIKit + private let elementsChainDelimiter = ";" + class PostHogAutocaptureIntegration: AutocaptureEventProcessing { private let postHogConfig: PostHogConfig private var debounceTimers: [Int: Timer] = [:] @@ -65,7 +67,6 @@ } } - private static let viewHierarchyDelimiter = ";" /** Handles the processing of autocapture events by extracting event details, building properties, and sending them to PostHog. @@ -90,20 +91,9 @@ properties["$screen_name"] = screenName } - let elements = event.viewHierarchy.map { node -> [String: Any] in - [ - "text": node.text, - "tag_name": node.targetClass, // required - "order": node.index, - "attributes": [ // required - "attr__class": node.targetClass, - ], - ].compactMapValues { $0 } - } - let elementsChain = event.viewHierarchy - .map(\.description) - .joined(separator: PostHogAutocaptureIntegration.viewHierarchyDelimiter) + .map(\.elementsChainEntry) + .joined(separator: elementsChainDelimiter) if let coordinates = event.touchCoordinates { properties["$touch_x"] = coordinates.x @@ -112,7 +102,6 @@ PostHogSDK.shared.autocapture( eventType: eventType, - elements: elements, elementsChain: elementsChain, properties: properties ) diff --git a/PostHog/Autocapture/SwiftUI/View+PostHogLabel.swift b/PostHog/Autocapture/SwiftUI/View+PostHogLabel.swift new file mode 100644 index 000000000..5ddc8ba52 --- /dev/null +++ b/PostHog/Autocapture/SwiftUI/View+PostHogLabel.swift @@ -0,0 +1,141 @@ +// +// View+PostHogLabel.swift +// PostHog +// +// Created by Yiannis Josephides on 04/12/2024. +// + +#if os(iOS) || targetEnvironment(macCatalyst) + import SwiftUI + + public extension View { + /** + Adds a custom label to this view for use with PostHog's auto-capture functionality. + + By setting a custom label, you can easily identify and filter interactions with this specific element in your analytics data. + + ### Usage + ```swift + struct ContentView: View { + var body: some View { + Button("Login") { + ... + } + .postHogLabel("loginButton") + } + } + ``` + + - Parameter label: A custom label that uniquely identifies the element for analytics purposes. + */ + func postHogLabel(_ label: String?) -> some View { + modifier(PostHogLabelTaggerViewModifier(label: label)) + } + } + + private struct PostHogLabelTaggerViewModifier: ViewModifier { + let label: String? + + func body(content: Content) -> some View { + content + .background(PostHogLabelViewTagger(label: label)) + } + } + + private struct PostHogLabelViewTagger: UIViewRepresentable { + let label: String? + + func makeUIView(context _: Context) -> PostHogLabelTaggerView { + PostHogLabelTaggerView(label: label) + } + + func updateUIView(_: PostHogLabelTaggerView, context _: Context) { + // nothing + } + } + + private class PostHogLabelTaggerView: UIView { + private let label: String? + + init(label: String?) { + self.label = label + super.init(frame: .zero) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.didMoveToWindow() + + superview?.postHogNoCapture = true + postHogNoCapture = true + + // try to find a "taggable" cousin view in hierarchy + // + // ### Why cousin view? + // + // Because of SwiftUI-to-UIKit view bridging: + // + // OriginalView (SwiftUI) + // L SwiftUITextFieldRepresentable (ViewRepresentable) + // L UITextField (UIControl) <- we tag here + // L PostHogLabelViewTagger (ViewRepresentable) + // L PostHogLabelTaggerView (UIView) <- we are here + // + if let view = findCousinView(of: PostHogSwiftUITaggable.self) { + view.postHogLabel = label + } else { + // just tag grandparent view + // + // ### Why grandparent view? + // + // Because of SwiftUI-to-UIKit view bridging: + // OriginalView (SwiftUI) <- we tag here + // L PostHogLabelViewTagger (ViewRepresentable) + // L PostHogLabelTaggerView (UIView) <- we are here + // + superview?.superview?.postHogLabel = label + } + } + + private func findCousinView(of _: T.Type) -> T? { + for sibling in superview?.siblings() ?? [] { + if let match = sibling.child(of: T.self) { + return match + } + } + return nil + } + } + + // MARK: - Helpers + + private extension UIView { + func siblings() -> [UIView] { + superview?.subviews.reduce(into: []) { r, c in + if c !== self { r.append(c) } + } ?? [] + } + + func child(of type: T.Type) -> T? { + for child in subviews { + if let curT = child as? T ?? child.child(of: type) { + return curT + } + } + return nil + } + } + + protocol PostHogSwiftUITaggable: UIView { /**/ } + + extension UIControl: PostHogSwiftUITaggable { /**/ } + extension UIPickerView: PostHogSwiftUITaggable { /**/ } + extension UITextView: PostHogSwiftUITaggable { /**/ } + extension UICollectionView: PostHogSwiftUITaggable { /**/ } + extension UITableView: PostHogSwiftUITaggable { /**/ } + +#endif diff --git a/PostHog/Autocapture/UIView+PostHogLabel.swift b/PostHog/Autocapture/UIView+PostHogLabel.swift new file mode 100644 index 000000000..c06d099e0 --- /dev/null +++ b/PostHog/Autocapture/UIView+PostHogLabel.swift @@ -0,0 +1,29 @@ +// +// UIView+PostHogLabel.swift +// PostHog +// +// Created by Yiannis Josephides on 04/12/2024. +// + +#if canImport(UIKit) + import UIKit + + public extension UIView { + /** + Adds a custom label to this view for use with PostHog's auto-capture functionality. + + By setting a custom label, you can easily identify and filter interactions with this specific element in your analytics data. + + ### Usage + ```swift + let myView = UIView() + myView.postHogLabel = "customLabel" + ``` + */ + var postHogLabel: String? { + get { objc_getAssociatedObject(self, &AssociatedKeys.phLabel) as? String } + set { objc_setAssociatedObject(self, &AssociatedKeys.phLabel, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + } + } + +#endif diff --git a/PostHog/PostHogMaskViewModifier.swift b/PostHog/PostHogMaskViewModifier.swift index 30e8cd127..e9b8605d3 100644 --- a/PostHog/PostHogMaskViewModifier.swift +++ b/PostHog/PostHogMaskViewModifier.swift @@ -43,25 +43,20 @@ private class PostHogMaskViewTaggerView: UIView { override func didMoveToSuperview() { super.didMoveToSuperview() - superview?.phIsManuallyMasked = true + // ### Why grandparent view? + // + // Because of SwiftUI-to-UIKit view bridging: + // OriginalView (SwiftUI) <- we tag here + // L PostHogMaskViewTagger (ViewRepresentable) + // L PostHogMaskViewTaggerView (UIView) <- we are here + superview?.superview?.postHogNoCapture = true } } - private var phIsManuallyMaskedKey: UInt8 = 0 extension UIView { - var phIsManuallyMasked: Bool { - get { - objc_getAssociatedObject(self, &phIsManuallyMaskedKey) as? Bool ?? false - } - - set { - objc_setAssociatedObject( - self, - &phIsManuallyMaskedKey, - newValue as Bool?, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - } + var postHogNoCapture: Bool { + get { objc_getAssociatedObject(self, &AssociatedKeys.phNoCapture) as? Bool ?? false } + set { objc_setAssociatedObject(self, &AssociatedKeys.phNoCapture, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } #endif diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 613280930..f90272584 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -405,7 +405,7 @@ let maxRetryDelay = 30.0 return } - let sanitizedProps = sanitizeDicionary(properties) + let sanitizedProps = sanitizeDictionary(properties) if sanitizedProps == nil { return } @@ -482,7 +482,7 @@ let maxRetryDelay = 30.0 let properties = buildProperties(distinctId: distinctId, properties: [ "distinct_id": distinctId, "$anon_distinct_id": oldDistinctId, - ], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce)) + ], userProperties: sanitizeDictionary(userProperties), userPropertiesSetOnce: sanitizeDictionary(userPropertiesSetOnce)) let sanitizedProperties = sanitizeProperties(properties) queue.add(PostHogEvent( @@ -616,9 +616,9 @@ let maxRetryDelay = 30.0 } let properties = buildProperties(distinctId: eventDistinctId, - properties: sanitizeDicionary(properties), - userProperties: sanitizeDicionary(userProperties), - userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce), + properties: sanitizeDictionary(properties), + userProperties: sanitizeDictionary(userProperties), + userPropertiesSetOnce: sanitizeDictionary(userPropertiesSetOnce), groups: groups, appendSharedProps: !snapshotEvent) let sanitizedProperties = sanitizeProperties(properties) @@ -662,7 +662,7 @@ let maxRetryDelay = 30.0 let props = [ "$screen_name": screenTitle, - ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } + ].merging(sanitizeDictionary(properties) ?? [:]) { prop, _ in prop } let distinctId = getDistinctId() @@ -678,7 +678,6 @@ let maxRetryDelay = 30.0 func autocapture( eventType: String, - elements: [[String: Any]], elementsChain: String, properties: [String: Any] ) { @@ -696,9 +695,8 @@ let maxRetryDelay = 30.0 let props = [ "$event_type": eventType, - "$elements": elements, "$elements_chain": elementsChain, - ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } + ].merging(sanitizeDictionary(properties) ?? [:]) { prop, _ in prop } let distinctId = getDistinctId() @@ -794,7 +792,7 @@ let maxRetryDelay = 30.0 var props: [String: Any] = ["$group_type": type, "$group_key": key] - let groupProps = sanitizeDicionary(groupProperties) + let groupProps = sanitizeDictionary(groupProperties) if groupProps != nil { props["$group_set"] = groupProps @@ -834,7 +832,7 @@ let maxRetryDelay = 30.0 _ = groups([type: key]) - groupIdentify(type: type, key: key, groupProperties: sanitizeDicionary(groupProperties)) + groupIdentify(type: type, key: key, groupProperties: sanitizeDictionary(groupProperties)) } // FEATURE FLAGS diff --git a/PostHog/Replay/PostHogReplayIntegration.swift b/PostHog/Replay/PostHogReplayIntegration.swift index ec1284e08..03ebd93d4 100644 --- a/PostHog/Replay/PostHogReplayIntegration.swift +++ b/PostHog/Replay/PostHogReplayIntegration.swift @@ -339,7 +339,7 @@ } // manually masked views through view modifier `PostHogMaskViewModifier` - if view.phIsManuallyMasked { + if view.postHogNoCapture { maskableWidgets.append(view.toAbsoluteRect(window)) return } diff --git a/PostHog/Utils/AssociatedKeys.swift b/PostHog/Utils/AssociatedKeys.swift new file mode 100644 index 000000000..fe6bebb06 --- /dev/null +++ b/PostHog/Utils/AssociatedKeys.swift @@ -0,0 +1,14 @@ +// +// AssociatedKeys.swift +// PostHog +// +// Created by Yiannis Josephides on 04/12/2024. +// + +import Foundation + +enum AssociatedKeys { + static var phForwardingDelegate: UInt8 = 0 + static var phNoCapture: UInt8 = 0 + static var phLabel: UInt8 = 0 +} diff --git a/PostHog/Utils/DictUtils.swift b/PostHog/Utils/DictUtils.swift index 611716079..e0d061239 100644 --- a/PostHog/Utils/DictUtils.swift +++ b/PostHog/Utils/DictUtils.swift @@ -7,7 +7,7 @@ import Foundation -public func sanitizeDicionary(_ dict: [String: Any]?) -> [String: Any]? { +public func sanitizeDictionary(_ dict: [String: Any]?) -> [String: Any]? { if dict == nil || dict!.isEmpty { return nil }