diff --git a/apps/basic-integration/swift/hackers-ios/.env.example b/apps/basic-integration/swift/hackers-ios/.env.example
new file mode 100644
index 000000000..745d269ae
--- /dev/null
+++ b/apps/basic-integration/swift/hackers-ios/.env.example
@@ -0,0 +1,2 @@
+POSTHOG_PROJECT_TOKEN=your_posthog_project_token
+POSTHOG_HOST=https://your-posthog-host
diff --git a/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift b/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift
index 6e1933209..afe1f3516 100644
--- a/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift
+++ b/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift
@@ -6,6 +6,7 @@
//
import Data
+import PostHog
import Shared
import UIKit
@@ -13,6 +14,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
+ configurePostHog()
+
// Configure a modest shared URL cache to limit on-disk growth from image/HTTP caching
// This affects system components like AsyncImage that use URLSession.shared
let memoryCapacity = 64 * 1024 * 1024 // 64 MB
@@ -36,4 +39,37 @@ class AppDelegate: NSObject, UIApplicationDelegate {
return true
}
+
+ private func configurePostHog() {
+ guard let projectToken = Bundle.main.object(forInfoDictionaryKey: "POSTHOG_PROJECT_TOKEN") as? String,
+ !projectToken.isEmpty,
+ !projectToken.hasPrefix("$(")
+ else {
+ #if DEBUG
+ assertionFailure("POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once POSTHOG_PROJECT_TOKEN is configured")
+ #endif
+ return
+ }
+
+ guard let host = Bundle.main.object(forInfoDictionaryKey: "POSTHOG_HOST") as? String,
+ !host.isEmpty,
+ !host.hasPrefix("$(")
+ else {
+ #if DEBUG
+ assertionFailure("POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once POSTHOG_HOST is configured")
+ #endif
+ return
+ }
+
+ let config = PostHogConfig(projectToken: projectToken, host: host)
+ config.errorTrackingConfig.autoCapture = true
+ config.logs.serviceName = Bundle.main.bundleIdentifier ?? "Hackers"
+ #if DEBUG
+ config.logs.environment = "debug"
+ #else
+ config.logs.environment = "production"
+ #endif
+ PostHogSDK.shared.setup(config)
+ PostHogSDK.shared.logger?.info("PostHog log capture initialized")
+ }
}
diff --git a/apps/basic-integration/swift/hackers-ios/App/ContentView.swift b/apps/basic-integration/swift/hackers-ios/App/ContentView.swift
index a9959bced..224c420c1 100644
--- a/apps/basic-integration/swift/hackers-ios/App/ContentView.swift
+++ b/apps/basic-integration/swift/hackers-ios/App/ContentView.swift
@@ -10,6 +10,7 @@ import Comments
import DesignSystem
import Domain
import Feed
+import PostHog
import Settings
import Shared
import SwiftUI
@@ -79,12 +80,8 @@ struct MainContentView: View {
viewModel: settingsViewModel,
isAuthenticated: sessionService.authenticationState == .authenticated,
currentUsername: sessionService.username,
- onLogin: { username, password in
- _ = try await sessionService.authenticate(username: username, password: password)
- },
- onLogout: {
- sessionService.unauthenticate()
- },
+ onLogin: authenticate,
+ onLogout: logout,
onShowOnboarding: {
showOnboarding = true
}
@@ -101,12 +98,8 @@ struct MainContentView: View {
LoginView(
isAuthenticated: sessionService.authenticationState == .authenticated,
currentUsername: sessionService.username,
- onLogin: { username, password in
- _ = try await sessionService.authenticate(username: username, password: password)
- },
- onLogout: {
- sessionService.unauthenticate()
- },
+ onLogin: authenticate,
+ onLogout: logout,
textSize: settingsViewModel.textSize
)
.textScaling(for: settingsViewModel.textSize)
@@ -117,12 +110,8 @@ struct MainContentView: View {
viewModel: settingsViewModel,
isAuthenticated: sessionService.authenticationState == .authenticated,
currentUsername: sessionService.username,
- onLogin: { username, password in
- _ = try await sessionService.authenticate(username: username, password: password)
- },
- onLogout: {
- sessionService.unauthenticate()
- },
+ onLogin: authenticate,
+ onLogout: logout,
onShowOnboarding: {
showOnboarding = true
}
@@ -132,9 +121,7 @@ struct MainContentView: View {
}
.sheet(isPresented: $showOnboarding) {
onboardingCoordinator
- .makeOnboardingView {
- showOnboarding = false
- }
+ .makeOnboardingView(onDismiss: onboardingDismissed)
.textScaling(for: settingsViewModel.textSize)
.toastOverlay(toastPresenter)
}
@@ -143,6 +130,32 @@ struct MainContentView: View {
showOnboarding = true
}
}
+ .onReceive(NotificationCenter.default.publisher(for: .bookmarksDidChange)) { notification in
+ guard let isBookmarked = notification.userInfo?["isBookmarked"] as? Bool else { return }
+ PostHogSDK.shared.capture(isBookmarked ? "bookmark_added" : "bookmark_removed")
+ PostHogSDK.shared.logger?.info(
+ "Bookmark state changed",
+ attributes: ["is_bookmarked": isBookmarked]
+ )
+ }
+ }
+
+ private func authenticate(username: String, password: String) async throws {
+ _ = try await sessionService.authenticate(username: username, password: password)
+ PostHogSDK.shared.capture("login_succeeded")
+ PostHogSDK.shared.logger?.info("Authentication completed")
+ }
+
+ private func logout() {
+ PostHogSDK.shared.capture("logout_requested")
+ PostHogSDK.shared.logger?.info("Logout requested")
+ sessionService.unauthenticate()
+ }
+
+ private func onboardingDismissed() {
+ PostHogSDK.shared.capture("onboarding_dismissed")
+ PostHogSDK.shared.logger?.info("Onboarding dismissed")
+ showOnboarding = false
}
private var isPresentingModal: Bool {
diff --git a/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist b/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist
index eb20b4468..cd761d2cc 100644
--- a/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist
+++ b/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist
@@ -39,6 +39,10 @@
LSRequiresIPhoneOS
+ POSTHOG_HOST
+ $(POSTHOG_HOST)
+ POSTHOG_PROJECT_TOKEN
+ $(POSTHOG_PROJECT_TOKEN)
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj
index 25a264601..e6b90cc55 100644
--- a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj
+++ b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj
@@ -20,6 +20,7 @@
24F39F8B18AFB1150055F8DC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24F39F8A18AFB1150055F8DC /* CoreGraphics.framework */; };
24F39F8D18AFB1150055F8DC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24F39F8C18AFB1150055F8DC /* UIKit.framework */; };
DA78360FD99A4A1D9C1E759C /* Authentication in Frameworks */ = {isa = PBXBuildFile; productRef = B2F5BEA03AC34B9DB788D8E2 /* Authentication */; };
+ F0A1B2C3D4E5F60718293A4B /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = F0A1B2C3D4E5F60718293A4A /* PostHog */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -102,6 +103,7 @@
2406B38728AD083500CB396B /* SwiftSoup in Frameworks */,
241E7B412E511BC20013EEA1 /* Data in Frameworks */,
248256FB2E59D2540068E91E /* Networking in Frameworks */,
+ F0A1B2C3D4E5F60718293A4B /* PostHog in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -192,6 +194,7 @@
248256FD2E59D2660068E91E /* Feed */,
2482576D2E5B38190068E91E /* Onboarding */,
B2F5BEA03AC34B9DB788D8E2 /* Authentication */,
+ F0A1B2C3D4E5F60718293A4A /* PostHog */,
);
productName = Hackers2;
productReference = 24F39F8518AFB1150055F8DC /* Hackers.app */;
@@ -237,6 +240,7 @@
248256FC2E59D2660068E91E /* XCLocalSwiftPackageReference "Features/Feed" */,
2482576C2E5B38190068E91E /* XCLocalSwiftPackageReference "Features/Onboarding" */,
52A3FC99203E47A9B5690643 /* XCLocalSwiftPackageReference "Features/Authentication" */,
+ F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */,
);
productRefGroup = 24F39F8618AFB1150055F8DC /* Products */;
projectDirPath = "";
@@ -510,6 +514,8 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.2.3;
+ POSTHOG_HOST = https://us.i.posthog.com;
+ POSTHOG_PROJECT_TOKEN = phx_API_KEY_IS_HARDCODED;
PRODUCT_BUNDLE_IDENTIFIER = com.weiranzhang.Hackers;
PRODUCT_NAME = Hackers;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -541,6 +547,8 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.2.3;
+ POSTHOG_HOST = https://us.i.posthog.com;
+ POSTHOG_PROJECT_TOKEN = phx_API_KEY_IS_HARDCODED;
PRODUCT_BUNDLE_IDENTIFIER = com.weiranzhang.Hackers;
PRODUCT_NAME = Hackers;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -631,6 +639,14 @@
minimumVersion = 2.0.0;
};
};
+ F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/PostHog/posthog-ios.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 3.92.7;
+ };
+ };
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -671,6 +687,11 @@
isa = XCSwiftPackageProductDependency;
productName = Authentication;
};
+ F0A1B2C3D4E5F60718293A4A /* PostHog */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */;
+ productName = PostHog;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = 24F39F7D18AFB1150055F8DC /* Project object */;
diff --git a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index aedcb4505..96a65be7d 100644
--- a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,6 +1,15 @@
{
- "originHash" : "944ecf5d94500e9cadaca6ec72c8aa21820f6f136372c503cd78c667c3a9c5be",
+ "originHash" : "8c1fc4163eedffd94bd8664fb0ed4270742d7b7806448a31b0b28523cb8a6f31",
"pins" : [
+ {
+ "identity" : "posthog-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/PostHog/posthog-ios.git",
+ "state" : {
+ "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083",
+ "version" : "3.92.7"
+ }
+ },
{
"identity" : "swiftsoup",
"kind" : "remoteSourceControl",